From 1e9d21eb341bfc02c0a791d575923e6c3d301e14 Mon Sep 17 00:00:00 2001 From: Michael Ratcliffe Date: Fri, 27 Oct 2017 15:33:10 +0100 Subject: [PATCH] Bug 1412311 - DevTools Shared Components to ES6 classes r=nchevobbe In devtools/client/shared/components/tree/TreeView.js I have had to leave defaultProps outside the getter as a temporary workaround for bug 1413167. MozReview-Commit-ID: 1yaxqFnC92p --HG-- extra : rebase_source : 64cae084e3edbb71e2b6948d69459bd82705c040 --- .../shared/components/AutoCompletePopup.js | 67 ++-- devtools/client/shared/components/Frame.js | 76 ++-- .../client/shared/components/HSplitBox.js | 76 ++-- .../shared/components/NotificationBox.js | 127 +++--- .../client/shared/components/SearchBox.js | 59 +-- .../client/shared/components/SidebarToggle.js | 48 +-- .../client/shared/components/StackTrace.js | 40 +- devtools/client/shared/components/Tree.js | 363 ++++++++++-------- .../shared/components/splitter/Draggable.js | 35 +- .../shared/components/splitter/SplitBox.js | 104 ++--- .../client/shared/components/tabs/TabBar.js | 103 ++--- .../client/shared/components/tabs/Tabs.js | 142 +++---- .../test/mochitest/test_tabs_menu.html | 16 +- .../shared/components/tree/LabelCell.js | 31 +- .../client/shared/components/tree/TreeCell.js | 53 +-- .../shared/components/tree/TreeHeader.js | 44 ++- .../client/shared/components/tree/TreeRow.js | 94 ++--- .../client/shared/components/tree/TreeView.js | 317 +++++++-------- 18 files changed, 960 insertions(+), 835 deletions(-) diff --git a/devtools/client/shared/components/AutoCompletePopup.js b/devtools/client/shared/components/AutoCompletePopup.js index 3136deabbc22..167ee3463ceb 100644 --- a/devtools/client/shared/components/AutoCompletePopup.js +++ b/devtools/client/shared/components/AutoCompletePopup.js @@ -4,48 +4,55 @@ "use strict"; -const { DOM: dom, createClass, PropTypes } = require("devtools/client/shared/vendor/react"); +const { DOM: dom, Component, PropTypes } = require("devtools/client/shared/vendor/react"); -module.exports = createClass({ - displayName: "AutocompletePopup", +class AutocompletePopup extends Component { + static get propTypes() { + return { + /** + * autocompleteProvider takes search-box's entire input text as `filter` argument + * ie. "is:cached pr" + * returned value is array of objects like below + * [{value: "is:cached protocol", displayValue: "protocol"}[, ...]] + * `value` is used to update the search-box input box for given item + * `displayValue` is used to render the autocomplete list + */ + autocompleteProvider: PropTypes.func.isRequired, + filter: PropTypes.string.isRequired, + onItemSelected: PropTypes.func.isRequired, + }; + } - propTypes: { - /** - * autocompleteProvider takes search-box's entire input text as `filter` argument - * ie. "is:cached pr" - * returned value is array of objects like below - * [{value: "is:cached protocol", displayValue: "protocol"}[, ...]] - * `value` is used to update the search-box input box for given item - * `displayValue` is used to render the autocomplete list - */ - autocompleteProvider: PropTypes.func.isRequired, - filter: PropTypes.string.isRequired, - onItemSelected: PropTypes.func.isRequired, - }, - - getInitialState() { - return this.computeState(this.props); - }, + constructor(props, context) { + super(props, context); + this.state = this.computeState(props); + this.computeState = this.computeState.bind(this); + this.jumpToTop = this.jumpToTop.bind(this); + this.jumpToBottom = this.jumpToBottom.bind(this); + this.jumpBy = this.jumpBy.bind(this); + this.select = this.select.bind(this); + this.onMouseDown = this.onMouseDown.bind(this); + } componentWillReceiveProps(nextProps) { if (this.props.filter === nextProps.filter) { return; } this.setState(this.computeState(nextProps)); - }, + } componentDidUpdate() { if (this.refs.selected) { this.refs.selected.scrollIntoView(false); } - }, + } computeState({ autocompleteProvider, filter }) { let list = autocompleteProvider(filter); let selectedIndex = list.length == 1 ? 0 : -1; return { list, selectedIndex }; - }, + } /** * Use this method to select the top-most item @@ -53,7 +60,7 @@ module.exports = createClass({ */ jumpToTop() { this.setState({ selectedIndex: 0 }); - }, + } /** * Use this method to select the bottom-most item @@ -61,7 +68,7 @@ module.exports = createClass({ */ jumpToBottom() { this.setState({ selectedIndex: this.state.list.length - 1 }); - }, + } /** * Increment the selected index with the provided increment value. Will cycle to the @@ -81,7 +88,7 @@ module.exports = createClass({ nextIndex = nextIndex < 0 ? list.length - 1 : nextIndex; } this.setState({selectedIndex: nextIndex}); - }, + } /** * Submit the currently selected item to the onItemSelected callback @@ -91,12 +98,12 @@ module.exports = createClass({ if (this.refs.selected) { this.props.onItemSelected(this.refs.selected.dataset.value); } - }, + } onMouseDown(e) { e.preventDefault(); this.setState({ selectedIndex: Number(e.target.dataset.index) }, this.select); - }, + } render() { let { list } = this.state; @@ -124,4 +131,6 @@ module.exports = createClass({ ) ); } -}); +} + +module.exports = AutocompletePopup; diff --git a/devtools/client/shared/components/Frame.js b/devtools/client/shared/components/Frame.js index 28c687eeca67..3fcb846f49c9 100644 --- a/devtools/client/shared/components/Frame.js +++ b/devtools/client/shared/components/Frame.js @@ -4,7 +4,7 @@ "use strict"; -const { DOM: dom, createClass, PropTypes } = require("devtools/client/shared/vendor/react"); +const { DOM: dom, Component, PropTypes } = require("devtools/client/shared/vendor/react"); const { getSourceNames, parseURL, isScratchpadScheme, getSourceMappedFile } = require("devtools/client/shared/source-utils"); const { LocalizationHelper } = require("devtools/shared/l10n"); @@ -12,34 +12,34 @@ const { LocalizationHelper } = require("devtools/shared/l10n"); const l10n = new LocalizationHelper("devtools/client/locales/components.properties"); const webl10n = new LocalizationHelper("devtools/client/locales/webconsole.properties"); -module.exports = createClass({ - displayName: "Frame", +class Frame extends Component { + static get propTypes() { + return { + // SavedFrame, or an object containing all the required properties. + frame: PropTypes.shape({ + functionDisplayName: PropTypes.string, + source: PropTypes.string.isRequired, + line: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]), + column: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]), + }).isRequired, + // Clicking on the frame link -- probably should link to the debugger. + onClick: PropTypes.func.isRequired, + // Option to display a function name before the source link. + showFunctionName: PropTypes.bool, + // Option to display a function name even if it's anonymous. + showAnonymousFunctionName: PropTypes.bool, + // Option to display a host name after the source link. + showHost: PropTypes.bool, + // Option to display a host name if the filename is empty or just '/' + showEmptyPathAsHost: PropTypes.bool, + // Option to display a full source instead of just the filename. + showFullSourceUrl: PropTypes.bool, + // Service to enable the source map feature for console. + sourceMapService: PropTypes.object, + }; + } - propTypes: { - // SavedFrame, or an object containing all the required properties. - frame: PropTypes.shape({ - functionDisplayName: PropTypes.string, - source: PropTypes.string.isRequired, - line: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]), - column: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]), - }).isRequired, - // Clicking on the frame link -- probably should link to the debugger. - onClick: PropTypes.func.isRequired, - // Option to display a function name before the source link. - showFunctionName: PropTypes.bool, - // Option to display a function name even if it's anonymous. - showAnonymousFunctionName: PropTypes.bool, - // Option to display a host name after the source link. - showHost: PropTypes.bool, - // Option to display a host name if the filename is empty or just '/' - showEmptyPathAsHost: PropTypes.bool, - // Option to display a full source instead of just the filename. - showFullSourceUrl: PropTypes.bool, - // Service to enable the source map feature for console. - sourceMapService: PropTypes.object, - }, - - getDefaultProps() { + static get defaultProps() { return { showFunctionName: false, showAnonymousFunctionName: false, @@ -47,7 +47,13 @@ module.exports = createClass({ showEmptyPathAsHost: false, showFullSourceUrl: false, }; - }, + } + + constructor(props) { + super(props); + this._locationChanged = this._locationChanged.bind(this); + this.getSourceForClick = this.getSourceForClick.bind(this); + } componentWillMount() { if (this.props.sourceMapService) { @@ -55,7 +61,7 @@ module.exports = createClass({ this.props.sourceMapService.subscribe(source, line, column, this._locationChanged); } - }, + } componentWillUnmount() { if (this.props.sourceMapService) { @@ -63,7 +69,7 @@ module.exports = createClass({ this.props.sourceMapService.unsubscribe(source, line, column, this._locationChanged); } - }, + } _locationChanged(isSourceMapped, url, line, column) { let newState = { @@ -79,7 +85,7 @@ module.exports = createClass({ } this.setState(newState); - }, + } /** * Utility method to convert the Frame object model to the @@ -95,7 +101,7 @@ module.exports = createClass({ column, functionDisplayName: this.props.frame.functionDisplayName, }; - }, + } render() { let frame, isSourceMapped; @@ -235,4 +241,6 @@ module.exports = createClass({ return dom.span(attributes, ...elements); } -}); +} + +module.exports = Frame; diff --git a/devtools/client/shared/components/HSplitBox.js b/devtools/client/shared/components/HSplitBox.js index 402e1a9cb4ea..68e228eeca6e 100644 --- a/devtools/client/shared/components/HSplitBox.js +++ b/devtools/client/shared/components/HSplitBox.js @@ -25,61 +25,67 @@ const { DOM: dom, - createClass, + Component, PropTypes, } = require("devtools/client/shared/vendor/react"); const { assert } = require("devtools/shared/DevToolsUtils"); -module.exports = createClass({ - displayName: "HSplitBox", +class HSplitBox extends Component { + static get propTypes() { + return { + // The contents of the start pane. + start: PropTypes.any.isRequired, - propTypes: { - // The contents of the start pane. - start: PropTypes.any.isRequired, + // The contents of the end pane. + end: PropTypes.any.isRequired, - // The contents of the end pane. - end: PropTypes.any.isRequired, + // The relative width of the start pane, expressed as a number between 0 and + // 1. The relative width of the end pane is 1 - startWidth. For example, + // with startWidth = .5, both panes are of equal width; with startWidth = + // .25, the start panel will take up 1/4 width and the end panel will take + // up 3/4 width. + startWidth: PropTypes.number, - // The relative width of the start pane, expressed as a number between 0 and - // 1. The relative width of the end pane is 1 - startWidth. For example, - // with startWidth = .5, both panes are of equal width; with startWidth = - // .25, the start panel will take up 1/4 width and the end panel will take - // up 3/4 width. - startWidth: PropTypes.number, + // A minimum css width value for the start and end panes. + minStartWidth: PropTypes.any, + minEndWidth: PropTypes.any, - // A minimum css width value for the start and end panes. - minStartWidth: PropTypes.any, - minEndWidth: PropTypes.any, + // A callback fired when the user drags the splitter to resize the relative + // pane widths. The function is passed the startWidth value that would put + // the splitter underneath the users mouse. + onResize: PropTypes.func.isRequired, + }; + } - // A callback fired when the user drags the splitter to resize the relative - // pane widths. The function is passed the startWidth value that would put - // the splitter underneath the users mouse. - onResize: PropTypes.func.isRequired, - }, - - getDefaultProps() { + static get defaultProps() { return { startWidth: 0.5, minStartWidth: "20px", minEndWidth: "20px", }; - }, + } - getInitialState() { - return { + constructor(props) { + super(props); + + this.state = { mouseDown: false }; - }, + + this._onMouseDown = this._onMouseDown.bind(this); + this._onMouseUp = this._onMouseUp.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + } componentDidMount() { document.defaultView.top.addEventListener("mouseup", this._onMouseUp); document.defaultView.top.addEventListener("mousemove", this._onMouseMove); - }, + } componentWillUnmount() { document.defaultView.top.removeEventListener("mouseup", this._onMouseUp); document.defaultView.top.removeEventListener("mousemove", this._onMouseMove); - }, + } _onMouseDown(event) { if (event.button !== 0) { @@ -88,7 +94,7 @@ module.exports = createClass({ this.setState({ mouseDown: true }); event.preventDefault(); - }, + } _onMouseUp(event) { if (event.button !== 0 || !this.state.mouseDown) { @@ -97,7 +103,7 @@ module.exports = createClass({ this.setState({ mouseDown: false }); event.preventDefault(); - }, + } _onMouseMove(event) { if (!this.state.mouseDown) { @@ -113,7 +119,7 @@ module.exports = createClass({ this.props.onResize(relative / width); event.preventDefault(); - }, + } render() { /* eslint-disable no-shadow */ @@ -149,4 +155,6 @@ module.exports = createClass({ ) ); } -}); +} + +module.exports = HSplitBox; diff --git a/devtools/client/shared/components/NotificationBox.js b/devtools/client/shared/components/NotificationBox.js index 838d2ac297bf..82f11243141d 100644 --- a/devtools/client/shared/components/NotificationBox.js +++ b/devtools/client/shared/components/NotificationBox.js @@ -10,7 +10,7 @@ const { LocalizationHelper } = require("devtools/shared/l10n"); const l10n = new LocalizationHelper("devtools/client/locales/components.properties"); // Shortcuts -const { PropTypes, createClass, DOM } = React; +const { PropTypes, Component, DOM } = React; const { div, span, button } = DOM; // Priority Levels @@ -34,72 +34,81 @@ const PriorityLevels = { * See also MDN for more info about : * https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/notificationbox */ -var NotificationBox = createClass({ - displayName: "NotificationBox", - - propTypes: { - // List of notifications appended into the box. - notifications: PropTypes.arrayOf(PropTypes.shape({ - // label to appear on the notification. - label: PropTypes.string.isRequired, - - // Value used to identify the notification - value: PropTypes.string.isRequired, - - // URL of image to appear on the notification. If "" then an icon - // appropriate for the priority level is used. - image: PropTypes.string.isRequired, - - // Notification priority; see Priority Levels. - priority: PropTypes.number.isRequired, - - // Array of button descriptions to appear on the notification. - buttons: PropTypes.arrayOf(PropTypes.shape({ - // Function to be called when the button is activated. - // This function is passed three arguments: - // 1) the NotificationBox component the button is associated with - // 2) the button description as passed to appendNotification. - // 3) the element which was the target of the button press event. - // If the return value from this function is not True, then the - // notification is closed. The notification is also not closed - // if an error is thrown. - callback: PropTypes.func.isRequired, - - // The label to appear on the button. +class NotificationBox extends Component { + static get propTypes() { + return { + // List of notifications appended into the box. + notifications: PropTypes.arrayOf(PropTypes.shape({ + // label to appear on the notification. label: PropTypes.string.isRequired, - // The accesskey attribute set on the