mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-10 20:05:49 +00:00
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
This commit is contained in:
parent
0f0d2ca958
commit
1e9d21eb34
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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 <xul:notificationbox>:
|
||||
* 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 <button> element.
|
||||
accesskey: PropTypes.string,
|
||||
// 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.
|
||||
label: PropTypes.string.isRequired,
|
||||
|
||||
// The accesskey attribute set on the <button> element.
|
||||
accesskey: PropTypes.string,
|
||||
})),
|
||||
|
||||
// A function to call to notify you of interesting things that happen
|
||||
// with the notification box.
|
||||
eventCallback: PropTypes.func,
|
||||
})),
|
||||
|
||||
// A function to call to notify you of interesting things that happen
|
||||
// with the notification box.
|
||||
eventCallback: PropTypes.func,
|
||||
})),
|
||||
// Message that should be shown when hovering over the close button
|
||||
closeButtonTooltip: PropTypes.string
|
||||
};
|
||||
}
|
||||
|
||||
// Message that should be shown when hovering over the close button
|
||||
closeButtonTooltip: PropTypes.string
|
||||
},
|
||||
|
||||
getDefaultProps() {
|
||||
static get defaultProps() {
|
||||
return {
|
||||
closeButtonTooltip: l10n.getStr("notificationBox.closeTooltip")
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
notifications: new Immutable.OrderedMap()
|
||||
};
|
||||
},
|
||||
|
||||
this.appendNotification = this.appendNotification.bind(this);
|
||||
this.removeNotification = this.removeNotification.bind(this);
|
||||
this.getNotificationWithValue = this.getNotificationWithValue.bind(this);
|
||||
this.getCurrentNotification = this.getCurrentNotification.bind(this);
|
||||
this.close = this.close.bind(this);
|
||||
this.renderButton = this.renderButton.bind(this);
|
||||
this.renderNotification = this.renderNotification.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new notification and display it. If another notification is
|
||||
* already present with a higher priority, the new notification will be
|
||||
* added behind it. See `propTypes` for arguments description.
|
||||
*/
|
||||
appendNotification(label, value, image, priority, buttons = [],
|
||||
eventCallback) {
|
||||
appendNotification(label, value, image, priority, buttons = [], eventCallback) {
|
||||
// Priority level must be within expected interval
|
||||
// (see priority levels at the top of this file).
|
||||
if (priority < PriorityLevels.PRIORITY_INFO_LOW ||
|
||||
@ -137,14 +146,14 @@ var NotificationBox = createClass({
|
||||
this.setState({
|
||||
notifications: notifications
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove specific notification from the list.
|
||||
*/
|
||||
removeNotification(notification) {
|
||||
this.close(this.state.notifications.get(notification.value));
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object that represents a notification. It can be
|
||||
@ -163,11 +172,11 @@ var NotificationBox = createClass({
|
||||
this.close(notification);
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
getCurrentNotification() {
|
||||
return this.state.notifications.first();
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Close specified notification.
|
||||
@ -184,7 +193,7 @@ var NotificationBox = createClass({
|
||||
this.setState({
|
||||
notifications: this.state.notifications.remove(notification.value)
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a button. A notification can have a set of custom buttons.
|
||||
@ -210,7 +219,7 @@ var NotificationBox = createClass({
|
||||
props.label
|
||||
)
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a notification.
|
||||
@ -241,7 +250,7 @@ var NotificationBox = createClass({
|
||||
)
|
||||
)
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the top (highest priority) notification. Only one
|
||||
@ -256,8 +265,8 @@ var NotificationBox = createClass({
|
||||
return div({className: "notificationbox"},
|
||||
content
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.NotificationBox = NotificationBox;
|
||||
module.exports.PriorityLevels = PriorityLevels;
|
||||
|
@ -6,31 +6,36 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const { DOM: dom, createClass, PropTypes, createFactory } = require("devtools/client/shared/vendor/react");
|
||||
const { DOM: dom, Component, PropTypes, createFactory } = require("devtools/client/shared/vendor/react");
|
||||
const KeyShortcuts = require("devtools/client/shared/key-shortcuts");
|
||||
const AutocompletePopup = createFactory(require("devtools/client/shared/components/AutoCompletePopup"));
|
||||
|
||||
/**
|
||||
* A generic search box component for use across devtools
|
||||
*/
|
||||
module.exports = createClass({
|
||||
displayName: "SearchBox",
|
||||
|
||||
propTypes: {
|
||||
delay: PropTypes.number,
|
||||
keyShortcut: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
placeholder: PropTypes.string,
|
||||
type: PropTypes.string,
|
||||
autocompleteProvider: PropTypes.func,
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
class SearchBox extends Component {
|
||||
static get propTypes() {
|
||||
return {
|
||||
delay: PropTypes.number,
|
||||
keyShortcut: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
placeholder: PropTypes.string,
|
||||
type: PropTypes.string,
|
||||
autocompleteProvider: PropTypes.func,
|
||||
};
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
value: "",
|
||||
focused: false,
|
||||
};
|
||||
},
|
||||
|
||||
this.onChange = this.onChange.bind(this);
|
||||
this.onClearButtonClick = this.onClearButtonClick.bind(this);
|
||||
this.onFocus = this.onFocus.bind(this);
|
||||
this.onBlur = this.onBlur.bind(this);
|
||||
this.onKeyDown = this.onKeyDown.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (!this.props.keyShortcut) {
|
||||
@ -44,7 +49,7 @@ module.exports = createClass({
|
||||
event.preventDefault();
|
||||
this.refs.input.focus();
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.shortcuts) {
|
||||
@ -55,7 +60,7 @@ module.exports = createClass({
|
||||
if (this.searchTimeout) {
|
||||
clearTimeout(this.searchTimeout);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
onChange() {
|
||||
if (this.state.value !== this.refs.input.value) {
|
||||
@ -81,20 +86,20 @@ module.exports = createClass({
|
||||
this.searchTimeout = null;
|
||||
this.props.onChange(this.state.value);
|
||||
}, this.props.delay);
|
||||
},
|
||||
}
|
||||
|
||||
onClearButtonClick() {
|
||||
this.refs.input.value = "";
|
||||
this.onChange();
|
||||
},
|
||||
}
|
||||
|
||||
onFocus() {
|
||||
this.setState({ focused: true });
|
||||
},
|
||||
}
|
||||
|
||||
onBlur() {
|
||||
this.setState({ focused: false });
|
||||
},
|
||||
}
|
||||
|
||||
onKeyDown(e) {
|
||||
let { autocomplete } = this.refs;
|
||||
@ -131,7 +136,7 @@ module.exports = createClass({
|
||||
autocomplete.jumpToBottom();
|
||||
break;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
render() {
|
||||
let {
|
||||
@ -175,4 +180,6 @@ module.exports = createClass({
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = SearchBox;
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const { DOM, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
|
||||
const { DOM, Component, PropTypes } = require("devtools/client/shared/vendor/react");
|
||||
|
||||
// Shortcuts
|
||||
const { button } = DOM;
|
||||
@ -15,35 +15,39 @@ const { button } = DOM;
|
||||
* Sidebar toggle button. This button is used to exapand
|
||||
* and collapse Sidebar.
|
||||
*/
|
||||
var SidebarToggle = createClass({
|
||||
displayName: "SidebarToggle",
|
||||
|
||||
propTypes: {
|
||||
// Set to true if collapsed.
|
||||
collapsed: PropTypes.bool.isRequired,
|
||||
// Tooltip text used when the button indicates expanded state.
|
||||
collapsePaneTitle: PropTypes.string.isRequired,
|
||||
// Tooltip text used when the button indicates collapsed state.
|
||||
expandPaneTitle: PropTypes.string.isRequired,
|
||||
// Click callback
|
||||
onClick: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
getInitialState: function () {
|
||||
class SidebarToggle extends Component {
|
||||
static get propTypes() {
|
||||
return {
|
||||
collapsed: this.props.collapsed,
|
||||
// Set to true if collapsed.
|
||||
collapsed: PropTypes.bool.isRequired,
|
||||
// Tooltip text used when the button indicates expanded state.
|
||||
collapsePaneTitle: PropTypes.string.isRequired,
|
||||
// Tooltip text used when the button indicates collapsed state.
|
||||
expandPaneTitle: PropTypes.string.isRequired,
|
||||
// Click callback
|
||||
onClick: PropTypes.func.isRequired,
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
collapsed: props.collapsed,
|
||||
};
|
||||
|
||||
this.onClick = this.onClick.bind(this);
|
||||
}
|
||||
|
||||
// Events
|
||||
|
||||
onClick: function (event) {
|
||||
onClick(event) {
|
||||
this.props.onClick(event);
|
||||
},
|
||||
}
|
||||
|
||||
// Rendering
|
||||
|
||||
render: function () {
|
||||
render() {
|
||||
let title = this.state.collapsed ?
|
||||
this.props.expandPaneTitle :
|
||||
this.props.collapsePaneTitle;
|
||||
@ -61,6 +65,6 @@ var SidebarToggle = createClass({
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = SidebarToggle;
|
||||
|
@ -5,18 +5,18 @@
|
||||
"use strict";
|
||||
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const { DOM: dom, createClass, createFactory, PropTypes } = React;
|
||||
const { DOM: dom, Component, createFactory, PropTypes } = React;
|
||||
const { LocalizationHelper } = require("devtools/shared/l10n");
|
||||
const Frame = createFactory(require("./Frame"));
|
||||
|
||||
const l10n = new LocalizationHelper("devtools/client/locales/webconsole.properties");
|
||||
|
||||
const AsyncFrame = createFactory(createClass({
|
||||
displayName: "AsyncFrame",
|
||||
|
||||
propTypes: {
|
||||
asyncCause: PropTypes.string.isRequired
|
||||
},
|
||||
class AsyncFrameClass extends Component {
|
||||
static get propTypes() {
|
||||
return {
|
||||
asyncCause: PropTypes.string.isRequired
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
let { asyncCause } = this.props;
|
||||
@ -26,18 +26,18 @@ const AsyncFrame = createFactory(createClass({
|
||||
l10n.getFormatStr("stacktrace.asyncStack", asyncCause)
|
||||
);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
const StackTrace = createClass({
|
||||
displayName: "StackTrace",
|
||||
|
||||
propTypes: {
|
||||
stacktrace: PropTypes.array.isRequired,
|
||||
onViewSourceInDebugger: PropTypes.func.isRequired,
|
||||
onViewSourceInScratchpad: PropTypes.func,
|
||||
// Service to enable the source map feature.
|
||||
sourceMapService: PropTypes.object,
|
||||
},
|
||||
class StackTrace extends Component {
|
||||
static get propTypes() {
|
||||
return {
|
||||
stacktrace: PropTypes.array.isRequired,
|
||||
onViewSourceInDebugger: PropTypes.func.isRequired,
|
||||
onViewSourceInScratchpad: PropTypes.func,
|
||||
// Service to enable the source map feature.
|
||||
sourceMapService: PropTypes.object,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
let {
|
||||
@ -77,6 +77,8 @@ const StackTrace = createClass({
|
||||
|
||||
return dom.div({ className: "stack-trace" }, frames);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const AsyncFrame = createFactory(AsyncFrameClass);
|
||||
|
||||
module.exports = StackTrace;
|
||||
|
@ -5,7 +5,7 @@
|
||||
"use strict";
|
||||
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const { DOM: dom, createClass, createFactory, PropTypes } = React;
|
||||
const { DOM: dom, Component, createFactory, PropTypes } = React;
|
||||
|
||||
const AUTO_EXPAND_DEPTH = 0;
|
||||
const NUMBER_OF_OFFSCREEN_ITEMS = 1;
|
||||
@ -97,158 +97,176 @@ const NUMBER_OF_OFFSCREEN_ITEMS = 1;
|
||||
* }
|
||||
* });
|
||||
*/
|
||||
module.exports = createClass({
|
||||
displayName: "Tree",
|
||||
class Tree extends Component {
|
||||
static get propTypes() {
|
||||
return {
|
||||
// Required props
|
||||
|
||||
propTypes: {
|
||||
// Required props
|
||||
// A function to get an item's parent, or null if it is a root.
|
||||
//
|
||||
// Type: getParent(item: Item) -> Maybe<Item>
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// // The parent of this item is stored in its `parent` property.
|
||||
// getParent: item => item.parent
|
||||
getParent: PropTypes.func.isRequired,
|
||||
|
||||
// A function to get an item's parent, or null if it is a root.
|
||||
//
|
||||
// Type: getParent(item: Item) -> Maybe<Item>
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// // The parent of this item is stored in its `parent` property.
|
||||
// getParent: item => item.parent
|
||||
getParent: PropTypes.func.isRequired,
|
||||
// A function to get an item's children.
|
||||
//
|
||||
// Type: getChildren(item: Item) -> [Item]
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// // This item's children are stored in its `children` property.
|
||||
// getChildren: item => item.children
|
||||
getChildren: PropTypes.func.isRequired,
|
||||
|
||||
// A function to get an item's children.
|
||||
//
|
||||
// Type: getChildren(item: Item) -> [Item]
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// // This item's children are stored in its `children` property.
|
||||
// getChildren: item => item.children
|
||||
getChildren: PropTypes.func.isRequired,
|
||||
// A function which takes an item and ArrowExpander component instance and
|
||||
// returns a component, or text, or anything else that React considers
|
||||
// renderable.
|
||||
//
|
||||
// Type: renderItem(item: Item,
|
||||
// depth: Number,
|
||||
// isFocused: Boolean,
|
||||
// arrow: ReactComponent,
|
||||
// isExpanded: Boolean) -> ReactRenderable
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// renderItem: (item, depth, isFocused, arrow, isExpanded) => {
|
||||
// let className = "my-tree-item";
|
||||
// if (isFocused) {
|
||||
// className += " focused";
|
||||
// }
|
||||
// return dom.div(
|
||||
// {
|
||||
// className,
|
||||
// style: { marginLeft: depth * 10 + "px" }
|
||||
// },
|
||||
// arrow,
|
||||
// dom.span({ className: "my-tree-item-label" }, item.label)
|
||||
// );
|
||||
// },
|
||||
renderItem: PropTypes.func.isRequired,
|
||||
|
||||
// A function which takes an item and ArrowExpander component instance and
|
||||
// returns a component, or text, or anything else that React considers
|
||||
// renderable.
|
||||
//
|
||||
// Type: renderItem(item: Item,
|
||||
// depth: Number,
|
||||
// isFocused: Boolean,
|
||||
// arrow: ReactComponent,
|
||||
// isExpanded: Boolean) -> ReactRenderable
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// renderItem: (item, depth, isFocused, arrow, isExpanded) => {
|
||||
// let className = "my-tree-item";
|
||||
// if (isFocused) {
|
||||
// className += " focused";
|
||||
// }
|
||||
// return dom.div(
|
||||
// {
|
||||
// className,
|
||||
// style: { marginLeft: depth * 10 + "px" }
|
||||
// },
|
||||
// arrow,
|
||||
// dom.span({ className: "my-tree-item-label" }, item.label)
|
||||
// );
|
||||
// },
|
||||
renderItem: PropTypes.func.isRequired,
|
||||
// A function which returns the roots of the tree (forest).
|
||||
//
|
||||
// Type: getRoots() -> [Item]
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// // In this case, we only have one top level, root item. You could
|
||||
// // return multiple items if you have many top level items in your
|
||||
// // tree.
|
||||
// getRoots: () => [this.props.rootOfMyTree]
|
||||
getRoots: PropTypes.func.isRequired,
|
||||
|
||||
// A function which returns the roots of the tree (forest).
|
||||
//
|
||||
// Type: getRoots() -> [Item]
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// // In this case, we only have one top level, root item. You could
|
||||
// // return multiple items if you have many top level items in your
|
||||
// // tree.
|
||||
// getRoots: () => [this.props.rootOfMyTree]
|
||||
getRoots: PropTypes.func.isRequired,
|
||||
// A function to get a unique key for the given item. This helps speed up
|
||||
// React's rendering a *TON*.
|
||||
//
|
||||
// Type: getKey(item: Item) -> String
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// getKey: item => `my-tree-item-${item.uniqueId}`
|
||||
getKey: PropTypes.func.isRequired,
|
||||
|
||||
// A function to get a unique key for the given item. This helps speed up
|
||||
// React's rendering a *TON*.
|
||||
//
|
||||
// Type: getKey(item: Item) -> String
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// getKey: item => `my-tree-item-${item.uniqueId}`
|
||||
getKey: PropTypes.func.isRequired,
|
||||
// A function to get whether an item is expanded or not. If an item is not
|
||||
// expanded, then it must be collapsed.
|
||||
//
|
||||
// Type: isExpanded(item: Item) -> Boolean
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// isExpanded: item => item.expanded,
|
||||
isExpanded: PropTypes.func.isRequired,
|
||||
|
||||
// A function to get whether an item is expanded or not. If an item is not
|
||||
// expanded, then it must be collapsed.
|
||||
//
|
||||
// Type: isExpanded(item: Item) -> Boolean
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// isExpanded: item => item.expanded,
|
||||
isExpanded: PropTypes.func.isRequired,
|
||||
// The height of an item in the tree including margin and padding, in
|
||||
// pixels.
|
||||
itemHeight: PropTypes.number.isRequired,
|
||||
|
||||
// The height of an item in the tree including margin and padding, in
|
||||
// pixels.
|
||||
itemHeight: PropTypes.number.isRequired,
|
||||
// Optional props
|
||||
|
||||
// Optional props
|
||||
// The currently focused item, if any such item exists.
|
||||
focused: PropTypes.any,
|
||||
|
||||
// The currently focused item, if any such item exists.
|
||||
focused: PropTypes.any,
|
||||
// Handle when a new item is focused.
|
||||
onFocus: PropTypes.func,
|
||||
|
||||
// Handle when a new item is focused.
|
||||
onFocus: PropTypes.func,
|
||||
// The depth to which we should automatically expand new items.
|
||||
autoExpandDepth: PropTypes.number,
|
||||
|
||||
// The depth to which we should automatically expand new items.
|
||||
autoExpandDepth: PropTypes.number,
|
||||
// Note: the two properties below are mutually exclusive. Only one of the
|
||||
// label properties is necessary.
|
||||
// ID of an element whose textual content serves as an accessible label for
|
||||
// a tree.
|
||||
labelledby: PropTypes.string,
|
||||
// Accessibility label for a tree widget.
|
||||
label: PropTypes.string,
|
||||
|
||||
// Note: the two properties below are mutually exclusive. Only one of the
|
||||
// label properties is necessary.
|
||||
// ID of an element whose textual content serves as an accessible label for
|
||||
// a tree.
|
||||
labelledby: PropTypes.string,
|
||||
// Accessibility label for a tree widget.
|
||||
label: PropTypes.string,
|
||||
// Optional event handlers for when items are expanded or collapsed. Useful
|
||||
// for dispatching redux events and updating application state, maybe lazily
|
||||
// loading subtrees from a worker, etc.
|
||||
//
|
||||
// Type:
|
||||
// onExpand(item: Item)
|
||||
// onCollapse(item: Item)
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// onExpand: item => dispatchExpandActionToRedux(item)
|
||||
onExpand: PropTypes.func,
|
||||
onCollapse: PropTypes.func,
|
||||
};
|
||||
}
|
||||
|
||||
// Optional event handlers for when items are expanded or collapsed. Useful
|
||||
// for dispatching redux events and updating application state, maybe lazily
|
||||
// loading subtrees from a worker, etc.
|
||||
//
|
||||
// Type:
|
||||
// onExpand(item: Item)
|
||||
// onCollapse(item: Item)
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// onExpand: item => dispatchExpandActionToRedux(item)
|
||||
onExpand: PropTypes.func,
|
||||
onCollapse: PropTypes.func,
|
||||
},
|
||||
|
||||
getDefaultProps() {
|
||||
static get defaultProps() {
|
||||
return {
|
||||
autoExpandDepth: AUTO_EXPAND_DEPTH,
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
scroll: 0,
|
||||
height: window.innerHeight,
|
||||
seen: new Set(),
|
||||
};
|
||||
},
|
||||
|
||||
this._onExpand = oncePerAnimationFrame(this._onExpand).bind(this);
|
||||
this._onCollapse = oncePerAnimationFrame(this._onCollapse).bind(this);
|
||||
this._onScroll = oncePerAnimationFrame(this._onScroll).bind(this);
|
||||
this._focusPrevNode = oncePerAnimationFrame(this._focusPrevNode).bind(this);
|
||||
this._focusNextNode = oncePerAnimationFrame(this._focusNextNode).bind(this);
|
||||
this._focusParentNode = oncePerAnimationFrame(this._focusParentNode).bind(this);
|
||||
|
||||
this._autoExpand = this._autoExpand.bind(this);
|
||||
this._preventArrowKeyScrolling = this._preventArrowKeyScrolling.bind(this);
|
||||
this._updateHeight = this._updateHeight.bind(this);
|
||||
this._dfs = this._dfs.bind(this);
|
||||
this._dfsFromRoots = this._dfsFromRoots.bind(this);
|
||||
this._focus = this._focus.bind(this);
|
||||
this._onBlur = this._onBlur.bind(this);
|
||||
this._onKeyDown = this._onKeyDown.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
window.addEventListener("resize", this._updateHeight);
|
||||
this._autoExpand();
|
||||
this._updateHeight();
|
||||
},
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this._autoExpand();
|
||||
this._updateHeight();
|
||||
},
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener("resize", this._updateHeight);
|
||||
},
|
||||
}
|
||||
|
||||
_autoExpand() {
|
||||
if (!this.props.autoExpandDepth) {
|
||||
@ -279,7 +297,7 @@ module.exports = createClass({
|
||||
for (let i = 0; i < length; i++) {
|
||||
autoExpand(roots[i], 0);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
_preventArrowKeyScrolling(e) {
|
||||
switch (e.key) {
|
||||
@ -298,7 +316,7 @@ module.exports = createClass({
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the state's height based on clientHeight.
|
||||
@ -307,7 +325,7 @@ module.exports = createClass({
|
||||
this.setState({
|
||||
height: this.refs.tree.clientHeight
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a pre-order depth-first search from item.
|
||||
@ -332,7 +350,7 @@ module.exports = createClass({
|
||||
}
|
||||
|
||||
return traversal;
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a pre-order depth-first search over the whole forest.
|
||||
@ -347,7 +365,7 @@ module.exports = createClass({
|
||||
}
|
||||
|
||||
return traversal;
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Expands current row.
|
||||
@ -355,7 +373,7 @@ module.exports = createClass({
|
||||
* @param {Object} item
|
||||
* @param {Boolean} expandAllChildren
|
||||
*/
|
||||
_onExpand: oncePerAnimationFrame(function (item, expandAllChildren) {
|
||||
_onExpand(item, expandAllChildren) {
|
||||
if (this.props.onExpand) {
|
||||
this.props.onExpand(item);
|
||||
|
||||
@ -367,18 +385,18 @@ module.exports = createClass({
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapses current row.
|
||||
*
|
||||
* @param {Object} item
|
||||
*/
|
||||
_onCollapse: oncePerAnimationFrame(function (item) {
|
||||
_onCollapse(item) {
|
||||
if (this.props.onCollapse) {
|
||||
this.props.onCollapse(item);
|
||||
}
|
||||
}),
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the passed in item to be the focused item.
|
||||
@ -411,14 +429,14 @@ module.exports = createClass({
|
||||
if (this.props.onFocus) {
|
||||
this.props.onFocus(item);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the state to have no focused item.
|
||||
*/
|
||||
_onBlur() {
|
||||
this._focus(0, undefined);
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired on a scroll within the tree's container, updates
|
||||
@ -426,12 +444,12 @@ module.exports = createClass({
|
||||
*
|
||||
* @param {Event} e
|
||||
*/
|
||||
_onScroll: oncePerAnimationFrame(function (e) {
|
||||
_onScroll(e) {
|
||||
this.setState({
|
||||
scroll: Math.max(this.refs.tree.scrollTop, 0),
|
||||
height: this.refs.tree.clientHeight
|
||||
});
|
||||
}),
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles key down events in the tree's container.
|
||||
@ -476,12 +494,12 @@ module.exports = createClass({
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the previous node relative to the currently focused item, to focused.
|
||||
*/
|
||||
_focusPrevNode: oncePerAnimationFrame(function () {
|
||||
_focusPrevNode() {
|
||||
// Start a depth first search and keep going until we reach the currently
|
||||
// focused node. Focus the previous node in the DFS, if it exists. If it
|
||||
// doesn't exist, we're at the first node already.
|
||||
@ -505,13 +523,13 @@ module.exports = createClass({
|
||||
}
|
||||
|
||||
this._focus(prevIndex, prev);
|
||||
}),
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the down arrow key which will focus either the next child
|
||||
* or sibling row.
|
||||
*/
|
||||
_focusNextNode: oncePerAnimationFrame(function () {
|
||||
_focusNextNode() {
|
||||
// Start a depth first search and keep going until we reach the currently
|
||||
// focused node. Focus the next node in the DFS, if it exists. If it
|
||||
// doesn't exist, we're at the last node already.
|
||||
@ -530,13 +548,13 @@ module.exports = createClass({
|
||||
if (i + 1 < traversal.length) {
|
||||
this._focus(i + 1, traversal[i + 1].item);
|
||||
}
|
||||
}),
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the left arrow key, going back up to the current rows'
|
||||
* parent row.
|
||||
*/
|
||||
_focusParentNode: oncePerAnimationFrame(function () {
|
||||
_focusParentNode() {
|
||||
const parent = this.props.getParent(this.props.focused);
|
||||
if (!parent) {
|
||||
return;
|
||||
@ -552,7 +570,7 @@ module.exports = createClass({
|
||||
}
|
||||
|
||||
this._focus(parentIndex, parent);
|
||||
}),
|
||||
}
|
||||
|
||||
render() {
|
||||
const traversal = this._dfsFromRoots();
|
||||
@ -656,28 +674,28 @@ module.exports = createClass({
|
||||
nodes
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* An arrow that displays whether its node is expanded (▼) or collapsed
|
||||
* (▶). When its node has no children, it is hidden.
|
||||
*/
|
||||
const ArrowExpander = createFactory(createClass({
|
||||
displayName: "ArrowExpander",
|
||||
|
||||
propTypes: {
|
||||
item: PropTypes.any.isRequired,
|
||||
visible: PropTypes.bool.isRequired,
|
||||
expanded: PropTypes.bool.isRequired,
|
||||
onCollapse: PropTypes.func.isRequired,
|
||||
onExpand: PropTypes.func.isRequired,
|
||||
},
|
||||
class ArrowExpanderClass extends Component {
|
||||
static get propTypes() {
|
||||
return {
|
||||
item: PropTypes.any.isRequired,
|
||||
visible: PropTypes.bool.isRequired,
|
||||
expanded: PropTypes.bool.isRequired,
|
||||
onCollapse: PropTypes.func.isRequired,
|
||||
onExpand: PropTypes.func.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
return this.props.item !== nextProps.item
|
||||
|| this.props.visible !== nextProps.visible
|
||||
|| this.props.expanded !== nextProps.expanded;
|
||||
},
|
||||
}
|
||||
|
||||
render() {
|
||||
const attrs = {
|
||||
@ -699,24 +717,26 @@ const ArrowExpander = createFactory(createClass({
|
||||
|
||||
return dom.div(attrs);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
const TreeNode = createFactory(createClass({
|
||||
propTypes: {
|
||||
id: PropTypes.any.isRequired,
|
||||
focused: PropTypes.bool.isRequired,
|
||||
item: PropTypes.any.isRequired,
|
||||
expanded: PropTypes.bool.isRequired,
|
||||
hasChildren: PropTypes.bool.isRequired,
|
||||
onExpand: PropTypes.func.isRequired,
|
||||
index: PropTypes.number.isRequired,
|
||||
first: PropTypes.bool,
|
||||
last: PropTypes.bool,
|
||||
onClick: PropTypes.func,
|
||||
onCollapse: PropTypes.func.isRequired,
|
||||
depth: PropTypes.number.isRequired,
|
||||
renderItem: PropTypes.func.isRequired,
|
||||
},
|
||||
class TreeNodeClass extends Component {
|
||||
static get propTypes() {
|
||||
return {
|
||||
id: PropTypes.any.isRequired,
|
||||
focused: PropTypes.bool.isRequired,
|
||||
item: PropTypes.any.isRequired,
|
||||
expanded: PropTypes.bool.isRequired,
|
||||
hasChildren: PropTypes.bool.isRequired,
|
||||
onExpand: PropTypes.func.isRequired,
|
||||
index: PropTypes.number.isRequired,
|
||||
first: PropTypes.bool,
|
||||
last: PropTypes.bool,
|
||||
onClick: PropTypes.func,
|
||||
onCollapse: PropTypes.func.isRequired,
|
||||
depth: PropTypes.number.isRequired,
|
||||
renderItem: PropTypes.func.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const arrow = ArrowExpander({
|
||||
@ -769,7 +789,10 @@ const TreeNode = createFactory(createClass({
|
||||
this.props.expanded),
|
||||
);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
const ArrowExpander = createFactory(ArrowExpanderClass);
|
||||
const TreeNode = createFactory(TreeNodeClass);
|
||||
|
||||
/**
|
||||
* Create a function that calls the given function `fn` only once per animation
|
||||
@ -794,3 +817,5 @@ function oncePerAnimationFrame(fn) {
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = Tree;
|
||||
|
@ -6,18 +6,25 @@
|
||||
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const ReactDOM = require("devtools/client/shared/vendor/react-dom");
|
||||
const { DOM: dom, PropTypes } = React;
|
||||
const { Component, DOM: dom, PropTypes } = React;
|
||||
|
||||
const Draggable = React.createClass({
|
||||
displayName: "Draggable",
|
||||
class Draggable extends Component {
|
||||
static get propTypes() {
|
||||
return {
|
||||
onMove: PropTypes.func.isRequired,
|
||||
onStart: PropTypes.func,
|
||||
onStop: PropTypes.func,
|
||||
style: PropTypes.object,
|
||||
className: PropTypes.string
|
||||
};
|
||||
}
|
||||
|
||||
propTypes: {
|
||||
onMove: PropTypes.func.isRequired,
|
||||
onStart: PropTypes.func,
|
||||
onStop: PropTypes.func,
|
||||
style: PropTypes.object,
|
||||
className: PropTypes.string
|
||||
},
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.startDragging = this.startDragging.bind(this);
|
||||
this.onMove = this.onMove.bind(this);
|
||||
this.onUp = this.onUp.bind(this);
|
||||
}
|
||||
|
||||
startDragging(ev) {
|
||||
ev.preventDefault();
|
||||
@ -25,14 +32,14 @@ const Draggable = React.createClass({
|
||||
doc.addEventListener("mousemove", this.onMove);
|
||||
doc.addEventListener("mouseup", this.onUp);
|
||||
this.props.onStart && this.props.onStart();
|
||||
},
|
||||
}
|
||||
|
||||
onMove(ev) {
|
||||
ev.preventDefault();
|
||||
// Use viewport coordinates so, moving mouse over iframes
|
||||
// doesn't mangle (relative) coordinates.
|
||||
this.props.onMove(ev.clientX, ev.clientY);
|
||||
},
|
||||
}
|
||||
|
||||
onUp(ev) {
|
||||
ev.preventDefault();
|
||||
@ -40,7 +47,7 @@ const Draggable = React.createClass({
|
||||
doc.removeEventListener("mousemove", this.onMove);
|
||||
doc.removeEventListener("mouseup", this.onUp);
|
||||
this.props.onStop && this.props.onStop();
|
||||
},
|
||||
}
|
||||
|
||||
render() {
|
||||
return dom.div({
|
||||
@ -49,6 +56,6 @@ const Draggable = React.createClass({
|
||||
onMouseDown: this.startDragging
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = Draggable;
|
||||
|
@ -7,62 +7,68 @@
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const ReactDOM = require("devtools/client/shared/vendor/react-dom");
|
||||
const Draggable = React.createFactory(require("devtools/client/shared/components/splitter/Draggable"));
|
||||
const { DOM: dom, PropTypes } = React;
|
||||
const { Component, DOM: dom, PropTypes } = React;
|
||||
|
||||
/**
|
||||
* This component represents a Splitter. The splitter supports vertical
|
||||
* as well as horizontal mode.
|
||||
*/
|
||||
const SplitBox = React.createClass({
|
||||
displayName: "SplitBox",
|
||||
class SplitBox extends Component {
|
||||
static get propTypes() {
|
||||
return {
|
||||
// Custom class name. You can use more names separated by a space.
|
||||
className: PropTypes.string,
|
||||
// Initial size of controlled panel.
|
||||
initialSize: PropTypes.string,
|
||||
// Initial width of controlled panel.
|
||||
initialWidth: PropTypes.string,
|
||||
// Initial height of controlled panel.
|
||||
initialHeight: PropTypes.string,
|
||||
// Left/top panel
|
||||
startPanel: PropTypes.any,
|
||||
// Min panel size.
|
||||
minSize: PropTypes.string,
|
||||
// Max panel size.
|
||||
maxSize: PropTypes.string,
|
||||
// Right/bottom panel
|
||||
endPanel: PropTypes.any,
|
||||
// True if the right/bottom panel should be controlled.
|
||||
endPanelControl: PropTypes.bool,
|
||||
// Size of the splitter handle bar.
|
||||
splitterSize: PropTypes.string,
|
||||
// True if the splitter bar is vertical (default is vertical).
|
||||
vert: PropTypes.bool,
|
||||
// Style object.
|
||||
style: PropTypes.object,
|
||||
};
|
||||
}
|
||||
|
||||
propTypes: {
|
||||
// Custom class name. You can use more names separated by a space.
|
||||
className: PropTypes.string,
|
||||
// Initial size of controlled panel.
|
||||
initialSize: PropTypes.string,
|
||||
// Initial width of controlled panel.
|
||||
initialWidth: PropTypes.string,
|
||||
// Initial height of controlled panel.
|
||||
initialHeight: PropTypes.string,
|
||||
// Left/top panel
|
||||
startPanel: PropTypes.any,
|
||||
// Min panel size.
|
||||
minSize: PropTypes.string,
|
||||
// Max panel size.
|
||||
maxSize: PropTypes.string,
|
||||
// Right/bottom panel
|
||||
endPanel: PropTypes.any,
|
||||
// True if the right/bottom panel should be controlled.
|
||||
endPanelControl: PropTypes.bool,
|
||||
// Size of the splitter handle bar.
|
||||
splitterSize: PropTypes.string,
|
||||
// True if the splitter bar is vertical (default is vertical).
|
||||
vert: PropTypes.bool,
|
||||
// Style object.
|
||||
style: PropTypes.object,
|
||||
},
|
||||
|
||||
getDefaultProps() {
|
||||
static get defaultProps() {
|
||||
return {
|
||||
splitterSize: 5,
|
||||
vert: true,
|
||||
endPanelControl: false
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* The state stores the current orientation (vertical or horizontal)
|
||||
* and the current size (width/height). All these values can change
|
||||
* during the component's life time.
|
||||
*/
|
||||
getInitialState() {
|
||||
return {
|
||||
vert: this.props.vert,
|
||||
width: this.props.initialWidth || this.props.initialSize,
|
||||
height: this.props.initialHeight || this.props.initialSize
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
/**
|
||||
* The state stores the current orientation (vertical or horizontal)
|
||||
* and the current size (width/height). All these values can change
|
||||
* during the component's life time.
|
||||
*/
|
||||
this.state = {
|
||||
vert: props.vert,
|
||||
width: props.initialWidth || props.initialSize,
|
||||
height: props.initialHeight || props.initialSize
|
||||
};
|
||||
},
|
||||
|
||||
this.onStartMove = this.onStartMove.bind(this);
|
||||
this.onStopMove = this.onStopMove.bind(this);
|
||||
this.onMove = this.onMove.bind(this);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
let { vert } = nextProps;
|
||||
@ -70,7 +76,7 @@ const SplitBox = React.createClass({
|
||||
if (vert !== this.props.vert) {
|
||||
this.setState({ vert });
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
return nextState.width != this.state.width ||
|
||||
@ -82,7 +88,7 @@ const SplitBox = React.createClass({
|
||||
nextProps.minSize != this.props.minSize ||
|
||||
nextProps.maxSize != this.props.maxSize ||
|
||||
nextProps.splitterSize != this.props.splitterSize;
|
||||
},
|
||||
}
|
||||
|
||||
// Dragging Events
|
||||
|
||||
@ -102,7 +108,7 @@ const SplitBox = React.createClass({
|
||||
this.setState({
|
||||
defaultCursor: defaultCursor
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
onStopMove() {
|
||||
const splitBox = ReactDOM.findDOMNode(this);
|
||||
@ -110,7 +116,7 @@ const SplitBox = React.createClass({
|
||||
doc.documentElement.style.cursor = this.state.defaultCursor;
|
||||
|
||||
splitBox.classList.remove("dragging");
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust size of the controlled panel. Depending on the current
|
||||
@ -149,7 +155,7 @@ const SplitBox = React.createClass({
|
||||
height: size
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// Rendering
|
||||
|
||||
@ -226,6 +232,6 @@ const SplitBox = React.createClass({
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = SplitBox;
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const { DOM, createClass, PropTypes, createFactory } = require("devtools/client/shared/vendor/react");
|
||||
const { DOM, Component, PropTypes, createFactory } = require("devtools/client/shared/vendor/react");
|
||||
const Tabs = createFactory(require("devtools/client/shared/components/tabs/Tabs").Tabs);
|
||||
|
||||
const Menu = require("devtools/client/framework/menu");
|
||||
@ -20,37 +20,50 @@ const { div } = DOM;
|
||||
/**
|
||||
* Renders Tabbar component.
|
||||
*/
|
||||
let Tabbar = createClass({
|
||||
displayName: "Tabbar",
|
||||
class Tabbar extends Component {
|
||||
static get propTypes() {
|
||||
return {
|
||||
children: PropTypes.array,
|
||||
menuDocument: PropTypes.object,
|
||||
onSelect: PropTypes.func,
|
||||
showAllTabsMenu: PropTypes.bool,
|
||||
activeTabId: PropTypes.string,
|
||||
renderOnlySelected: PropTypes.bool,
|
||||
};
|
||||
}
|
||||
|
||||
propTypes: {
|
||||
children: PropTypes.array,
|
||||
menuDocument: PropTypes.object,
|
||||
onSelect: PropTypes.func,
|
||||
showAllTabsMenu: PropTypes.bool,
|
||||
activeTabId: PropTypes.string,
|
||||
renderOnlySelected: PropTypes.bool,
|
||||
},
|
||||
|
||||
getDefaultProps: function () {
|
||||
static get defaultProps() {
|
||||
return {
|
||||
menuDocument: window.parent.document,
|
||||
showAllTabsMenu: false,
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
getInitialState: function () {
|
||||
let { activeTabId, children = [] } = this.props;
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
let { activeTabId, children = [] } = props;
|
||||
let tabs = this.createTabs(children);
|
||||
let activeTab = tabs.findIndex((tab, index) => tab.id === activeTabId);
|
||||
|
||||
return {
|
||||
this.state = {
|
||||
activeTab: activeTab === -1 ? 0 : activeTab,
|
||||
tabs,
|
||||
};
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function (nextProps) {
|
||||
this.createTabs = this.createTabs.bind(this);
|
||||
this.addTab = this.addTab.bind(this);
|
||||
this.toggleTab = this.toggleTab.bind(this);
|
||||
this.removeTab = this.removeTab.bind(this);
|
||||
this.select = this.select.bind(this);
|
||||
this.getTabIndex = this.getTabIndex.bind(this);
|
||||
this.getTabId = this.getTabId.bind(this);
|
||||
this.getCurrentTabId = this.getCurrentTabId.bind(this);
|
||||
this.onTabChanged = this.onTabChanged.bind(this);
|
||||
this.onAllTabsMenuClick = this.onAllTabsMenuClick.bind(this);
|
||||
this.renderTab = this.renderTab.bind(this);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
let { activeTabId, children = [] } = nextProps;
|
||||
let tabs = this.createTabs(children);
|
||||
let activeTab = tabs.findIndex((tab, index) => tab.id === activeTabId);
|
||||
@ -62,9 +75,9 @@ let Tabbar = createClass({
|
||||
tabs,
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
createTabs: function (children) {
|
||||
createTabs(children) {
|
||||
return children
|
||||
.filter((panel) => panel)
|
||||
.map((panel, index) =>
|
||||
@ -74,11 +87,11 @@ let Tabbar = createClass({
|
||||
title: panel.props.title,
|
||||
})
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
// Public API
|
||||
|
||||
addTab: function (id, title, selected = false, panel, url, index = -1) {
|
||||
addTab(id, title, selected = false, panel, url, index = -1) {
|
||||
let tabs = this.state.tabs.slice();
|
||||
|
||||
if (index >= 0) {
|
||||
@ -100,9 +113,9 @@ let Tabbar = createClass({
|
||||
this.props.onSelect(id);
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
toggleTab: function (tabId, isVisible) {
|
||||
toggleTab(tabId, isVisible) {
|
||||
let index = this.getTabIndex(tabId);
|
||||
if (index < 0) {
|
||||
return;
|
||||
@ -116,9 +129,9 @@ let Tabbar = createClass({
|
||||
this.setState(Object.assign({}, this.state, {
|
||||
tabs: tabs,
|
||||
}));
|
||||
},
|
||||
}
|
||||
|
||||
removeTab: function (tabId) {
|
||||
removeTab(tabId) {
|
||||
let index = this.getTabIndex(tabId);
|
||||
if (index < 0) {
|
||||
return;
|
||||
@ -137,9 +150,9 @@ let Tabbar = createClass({
|
||||
tabs,
|
||||
activeTab,
|
||||
}));
|
||||
},
|
||||
}
|
||||
|
||||
select: function (tabId) {
|
||||
select(tabId) {
|
||||
let index = this.getTabIndex(tabId);
|
||||
if (index < 0) {
|
||||
return;
|
||||
@ -154,11 +167,11 @@ let Tabbar = createClass({
|
||||
this.props.onSelect(tabId);
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
getTabIndex: function (tabId) {
|
||||
getTabIndex(tabId) {
|
||||
let tabIndex = -1;
|
||||
this.state.tabs.forEach((tab, index) => {
|
||||
if (tab.id === tabId) {
|
||||
@ -166,19 +179,19 @@ let Tabbar = createClass({
|
||||
}
|
||||
});
|
||||
return tabIndex;
|
||||
},
|
||||
}
|
||||
|
||||
getTabId: function (index) {
|
||||
getTabId(index) {
|
||||
return this.state.tabs[index].id;
|
||||
},
|
||||
}
|
||||
|
||||
getCurrentTabId: function () {
|
||||
getCurrentTabId() {
|
||||
return this.state.tabs[this.state.activeTab].id;
|
||||
},
|
||||
}
|
||||
|
||||
// Event Handlers
|
||||
|
||||
onTabChanged: function (index) {
|
||||
onTabChanged(index) {
|
||||
this.setState({
|
||||
activeTab: index
|
||||
});
|
||||
@ -186,9 +199,9 @@ let Tabbar = createClass({
|
||||
if (this.props.onSelect) {
|
||||
this.props.onSelect(this.state.tabs[index].id);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
onAllTabsMenuClick: function (event) {
|
||||
onAllTabsMenuClick(event) {
|
||||
let menu = new Menu();
|
||||
let target = event.target;
|
||||
|
||||
@ -214,11 +227,11 @@ let Tabbar = createClass({
|
||||
{ doc: this.props.menuDocument });
|
||||
|
||||
return menu;
|
||||
},
|
||||
}
|
||||
|
||||
// Rendering
|
||||
|
||||
renderTab: function (tab) {
|
||||
renderTab(tab) {
|
||||
if (typeof tab.panel === "function") {
|
||||
return tab.panel({
|
||||
key: tab.id,
|
||||
@ -229,9 +242,9 @@ let Tabbar = createClass({
|
||||
}
|
||||
|
||||
return tab.panel;
|
||||
},
|
||||
}
|
||||
|
||||
render: function () {
|
||||
render() {
|
||||
let tabs = this.state.tabs.map((tab) => this.renderTab(tab));
|
||||
|
||||
return (
|
||||
@ -247,7 +260,7 @@ let Tabbar = createClass({
|
||||
)
|
||||
)
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Tabbar;
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
define(function (require, exports, module) {
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const { DOM } = React;
|
||||
const { Component, DOM } = React;
|
||||
const { findDOMNode } = require("devtools/client/shared/vendor/react-dom");
|
||||
|
||||
/**
|
||||
@ -31,43 +31,45 @@ define(function (require, exports, module) {
|
||||
* </div>
|
||||
* <div>
|
||||
*/
|
||||
let Tabs = React.createClass({
|
||||
displayName: "Tabs",
|
||||
class Tabs extends Component {
|
||||
static get propTypes() {
|
||||
return {
|
||||
className: React.PropTypes.oneOfType([
|
||||
React.PropTypes.array,
|
||||
React.PropTypes.string,
|
||||
React.PropTypes.object
|
||||
]),
|
||||
tabActive: React.PropTypes.number,
|
||||
onMount: React.PropTypes.func,
|
||||
onBeforeChange: React.PropTypes.func,
|
||||
onAfterChange: React.PropTypes.func,
|
||||
children: React.PropTypes.oneOfType([
|
||||
React.PropTypes.array,
|
||||
React.PropTypes.element
|
||||
]).isRequired,
|
||||
showAllTabsMenu: React.PropTypes.bool,
|
||||
onAllTabsMenuClick: React.PropTypes.func,
|
||||
|
||||
propTypes: {
|
||||
className: React.PropTypes.oneOfType([
|
||||
React.PropTypes.array,
|
||||
React.PropTypes.string,
|
||||
React.PropTypes.object
|
||||
]),
|
||||
tabActive: React.PropTypes.number,
|
||||
onMount: React.PropTypes.func,
|
||||
onBeforeChange: React.PropTypes.func,
|
||||
onAfterChange: React.PropTypes.func,
|
||||
children: React.PropTypes.oneOfType([
|
||||
React.PropTypes.array,
|
||||
React.PropTypes.element
|
||||
]).isRequired,
|
||||
showAllTabsMenu: React.PropTypes.bool,
|
||||
onAllTabsMenuClick: React.PropTypes.func,
|
||||
// Set true will only render selected panel on DOM. It's complete
|
||||
// opposite of the created array, and it's useful if panels content
|
||||
// is unpredictable and update frequently.
|
||||
renderOnlySelected: React.PropTypes.bool,
|
||||
};
|
||||
}
|
||||
|
||||
// Set true will only render selected panel on DOM. It's complete
|
||||
// opposite of the created array, and it's useful if panels content
|
||||
// is unpredictable and update frequently.
|
||||
renderOnlySelected: React.PropTypes.bool,
|
||||
},
|
||||
|
||||
getDefaultProps: function () {
|
||||
static get defaultProps() {
|
||||
return {
|
||||
tabActive: 0,
|
||||
showAllTabsMenu: false,
|
||||
renderOnlySelected: false,
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
getInitialState: function () {
|
||||
return {
|
||||
tabActive: this.props.tabActive,
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
tabActive: props.tabActive,
|
||||
|
||||
// This array is used to store an information whether a tab
|
||||
// at specific index has already been created (e.g. selected
|
||||
@ -82,9 +84,17 @@ define(function (require, exports, module) {
|
||||
// True if tabs can't fit into available horizontal space.
|
||||
overflow: false,
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function () {
|
||||
this.onOverflow = this.onOverflow.bind(this);
|
||||
this.onUnderflow = this.onUnderflow.bind(this);
|
||||
this.onKeyDown = this.onKeyDown.bind(this);
|
||||
this.onClickTab = this.onClickTab.bind(this);
|
||||
this.setActive = this.setActive.bind(this);
|
||||
this.renderMenuItems = this.renderMenuItems.bind(this);
|
||||
this.renderPanels = this.renderPanels.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
let node = findDOMNode(this);
|
||||
node.addEventListener("keydown", this.onKeyDown);
|
||||
|
||||
@ -101,9 +111,9 @@ define(function (require, exports, module) {
|
||||
if (this.props.onMount) {
|
||||
this.props.onMount(index);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
componentWillReceiveProps: function (nextProps) {
|
||||
componentWillReceiveProps(nextProps) {
|
||||
let { children, tabActive } = nextProps;
|
||||
|
||||
// Check type of 'tabActive' props to see if it's valid
|
||||
@ -123,9 +133,9 @@ define(function (require, exports, module) {
|
||||
tabActive,
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
componentWillUnmount: function () {
|
||||
componentWillUnmount() {
|
||||
let node = findDOMNode(this);
|
||||
node.removeEventListener("keydown", this.onKeyDown);
|
||||
|
||||
@ -133,27 +143,27 @@ define(function (require, exports, module) {
|
||||
node.removeEventListener("overflow", this.onOverflow);
|
||||
node.removeEventListener("underflow", this.onUnderflow);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// DOM Events
|
||||
|
||||
onOverflow: function (event) {
|
||||
onOverflow(event) {
|
||||
if (event.target.classList.contains("tabs-menu")) {
|
||||
this.setState({
|
||||
overflow: true
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
onUnderflow: function (event) {
|
||||
onUnderflow(event) {
|
||||
if (event.target.classList.contains("tabs-menu")) {
|
||||
this.setState({
|
||||
overflow: false
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
onKeyDown: function (event) {
|
||||
onKeyDown(event) {
|
||||
// Bail out if the focus isn't on a tab.
|
||||
if (!event.target.closest(".tabs-menu-item")) {
|
||||
return;
|
||||
@ -174,19 +184,19 @@ define(function (require, exports, module) {
|
||||
if (this.state.tabActive != tabActive) {
|
||||
this.setActive(tabActive);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
onClickTab: function (index, event) {
|
||||
onClickTab(index, event) {
|
||||
this.setActive(index);
|
||||
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// API
|
||||
|
||||
setActive: function (index) {
|
||||
setActive(index) {
|
||||
let onAfterChange = this.props.onAfterChange;
|
||||
let onBeforeChange = this.props.onBeforeChange;
|
||||
|
||||
@ -217,11 +227,11 @@ define(function (require, exports, module) {
|
||||
onAfterChange(index);
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
// Rendering
|
||||
|
||||
renderMenuItems: function () {
|
||||
renderMenuItems() {
|
||||
if (!this.props.children) {
|
||||
throw new Error("There must be at least one Tab");
|
||||
}
|
||||
@ -299,9 +309,9 @@ define(function (require, exports, module) {
|
||||
allTabsMenu
|
||||
)
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
renderPanels: function () {
|
||||
renderPanels() {
|
||||
let { children, renderOnlySelected } = this.props;
|
||||
|
||||
if (!children) {
|
||||
@ -359,38 +369,38 @@ define(function (require, exports, module) {
|
||||
panels
|
||||
)
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
render: function () {
|
||||
render() {
|
||||
return (
|
||||
DOM.div({ className: ["tabs", this.props.className].join(" ") },
|
||||
this.renderMenuItems(),
|
||||
this.renderPanels()
|
||||
)
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders simple tab 'panel'.
|
||||
*/
|
||||
let Panel = React.createClass({
|
||||
displayName: "Panel",
|
||||
class Panel extends Component {
|
||||
static get propTypes() {
|
||||
return {
|
||||
title: React.PropTypes.string.isRequired,
|
||||
children: React.PropTypes.oneOfType([
|
||||
React.PropTypes.array,
|
||||
React.PropTypes.element
|
||||
]).isRequired
|
||||
};
|
||||
}
|
||||
|
||||
propTypes: {
|
||||
title: React.PropTypes.string.isRequired,
|
||||
children: React.PropTypes.oneOfType([
|
||||
React.PropTypes.array,
|
||||
React.PropTypes.element
|
||||
]).isRequired
|
||||
},
|
||||
|
||||
render: function () {
|
||||
render() {
|
||||
return DOM.div({className: "tab-panel"},
|
||||
this.props.children
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
exports.TabPanel = Panel;
|
||||
|
@ -26,8 +26,8 @@ Test all-tabs menu.
|
||||
window.onload = Task.async(function* () {
|
||||
try {
|
||||
const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
|
||||
const React = browserRequire("devtools/client/shared/vendor/react");
|
||||
const Tabbar = React.createFactory(browserRequire("devtools/client/shared/components/tabs/TabBar"));
|
||||
const { Component, createFactory, DOM } = browserRequire("devtools/client/shared/vendor/react");
|
||||
const Tabbar = createFactory(browserRequire("devtools/client/shared/components/tabs/TabBar"));
|
||||
|
||||
// Create container for the TabBar. Set smaller width
|
||||
// to ensure that tabs won't fit and the all-tabs menu
|
||||
@ -45,12 +45,14 @@ window.onload = Task.async(function* () {
|
||||
|
||||
const tabbarReact = ReactDOM.render(tabbar, tabBarBox);
|
||||
|
||||
// Test panel.
|
||||
let TabPanel = React.createFactory(React.createClass({
|
||||
render: function () {
|
||||
return React.DOM.div({}, "content");
|
||||
class TabPanelClass extends Component {
|
||||
render() {
|
||||
return DOM.div({}, "content");
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
// Test panel.
|
||||
let TabPanel = createFactory(TabPanelClass);
|
||||
|
||||
// Create a few panels.
|
||||
yield addTabWithPanel(1);
|
||||
|
@ -8,26 +8,23 @@
|
||||
// Make this available to both AMD and CJS environments
|
||||
define(function (require, exports, module) {
|
||||
// ReactJS
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
|
||||
// Shortcuts
|
||||
const { td, span } = React.DOM;
|
||||
const PropTypes = React.PropTypes;
|
||||
const { Component, DOM: dom, PropTypes } =
|
||||
require("devtools/client/shared/vendor/react");
|
||||
|
||||
/**
|
||||
* Render the default cell used for toggle buttons
|
||||
*/
|
||||
let LabelCell = React.createClass({
|
||||
displayName: "LabelCell",
|
||||
|
||||
class LabelCell extends Component {
|
||||
// See the TreeView component for details related
|
||||
// to the 'member' object.
|
||||
propTypes: {
|
||||
id: PropTypes.string.isRequired,
|
||||
member: PropTypes.object.isRequired
|
||||
},
|
||||
static get propTypes() {
|
||||
return {
|
||||
id: PropTypes.string.isRequired,
|
||||
member: PropTypes.object.isRequired
|
||||
};
|
||||
}
|
||||
|
||||
render: function () {
|
||||
render() {
|
||||
let id = this.props.id;
|
||||
let member = this.props.member;
|
||||
let level = member.level || 0;
|
||||
@ -49,16 +46,16 @@ define(function (require, exports, module) {
|
||||
}
|
||||
|
||||
return (
|
||||
td({
|
||||
dom.td({
|
||||
className: "treeLabelCell",
|
||||
key: "default",
|
||||
style: rowStyle,
|
||||
role: "presentation"},
|
||||
span({
|
||||
dom.span({
|
||||
className: iconClassList.join(" "),
|
||||
role: "presentation"
|
||||
}),
|
||||
span({
|
||||
dom.span({
|
||||
className: "treeLabel " + member.type + "Label",
|
||||
"aria-labelledby": id,
|
||||
"data-level": level
|
||||
@ -66,7 +63,7 @@ define(function (require, exports, module) {
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
module.exports = LabelCell;
|
||||
|
@ -8,45 +8,48 @@
|
||||
// Make this available to both AMD and CJS environments
|
||||
define(function (require, exports, module) {
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
|
||||
// Shortcuts
|
||||
const { Component, PropTypes } = React;
|
||||
const { input, span, td } = React.DOM;
|
||||
const PropTypes = React.PropTypes;
|
||||
|
||||
/**
|
||||
* This template represents a cell in TreeView row. It's rendered
|
||||
* using <td> element (the row is <tr> and the entire tree is <table>).
|
||||
*/
|
||||
let TreeCell = React.createClass({
|
||||
displayName: "TreeCell",
|
||||
|
||||
class TreeCell extends Component {
|
||||
// See TreeView component for detailed property explanation.
|
||||
propTypes: {
|
||||
value: PropTypes.any,
|
||||
decorator: PropTypes.object,
|
||||
id: PropTypes.string.isRequired,
|
||||
member: PropTypes.object.isRequired,
|
||||
renderValue: PropTypes.func.isRequired,
|
||||
enableInput: PropTypes.bool,
|
||||
},
|
||||
|
||||
getInitialState: function () {
|
||||
static get propTypes() {
|
||||
return {
|
||||
value: PropTypes.any,
|
||||
decorator: PropTypes.object,
|
||||
id: PropTypes.string.isRequired,
|
||||
member: PropTypes.object.isRequired,
|
||||
renderValue: PropTypes.func.isRequired,
|
||||
enableInput: PropTypes.bool,
|
||||
};
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
inputEnabled: false,
|
||||
};
|
||||
},
|
||||
|
||||
this.getCellClass = this.getCellClass.bind(this);
|
||||
this.updateInputEnabled = this.updateInputEnabled.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimize cell rendering. Rerender cell content only if
|
||||
* the value or expanded state changes.
|
||||
*/
|
||||
shouldComponentUpdate: function (nextProps, nextState) {
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
return (this.props.value != nextProps.value) ||
|
||||
(this.state !== nextState) ||
|
||||
(this.props.member.open != nextProps.member.open);
|
||||
},
|
||||
}
|
||||
|
||||
getCellClass: function (object, id) {
|
||||
getCellClass(object, id) {
|
||||
let decorator = this.props.decorator;
|
||||
if (!decorator || !decorator.getCellClass) {
|
||||
return [];
|
||||
@ -63,15 +66,15 @@ define(function (require, exports, module) {
|
||||
}
|
||||
|
||||
return classNames;
|
||||
},
|
||||
}
|
||||
|
||||
updateInputEnabled: function (evt) {
|
||||
updateInputEnabled(evt) {
|
||||
this.setState(Object.assign({}, this.state, {
|
||||
inputEnabled: evt.target.nodeName.toLowerCase() !== "input",
|
||||
}));
|
||||
},
|
||||
}
|
||||
|
||||
render: function () {
|
||||
render() {
|
||||
let {
|
||||
member,
|
||||
id,
|
||||
@ -127,7 +130,7 @@ define(function (require, exports, module) {
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Default value rendering.
|
||||
let defaultRenderValue = props => {
|
||||
|
@ -7,39 +7,41 @@
|
||||
|
||||
// Make this available to both AMD and CJS environments
|
||||
define(function (require, exports, module) {
|
||||
// ReactJS
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
|
||||
// Shortcuts
|
||||
const { Component, PropTypes } = React;
|
||||
const { thead, tr, td, div } = React.DOM;
|
||||
const PropTypes = React.PropTypes;
|
||||
|
||||
/**
|
||||
* This component is responsible for rendering tree header.
|
||||
* It's based on <thead> element.
|
||||
*/
|
||||
let TreeHeader = React.createClass({
|
||||
displayName: "TreeHeader",
|
||||
|
||||
class TreeHeader extends Component {
|
||||
// See also TreeView component for detailed info about properties.
|
||||
propTypes: {
|
||||
// Custom tree decorator
|
||||
decorator: PropTypes.object,
|
||||
// True if the header should be visible
|
||||
header: PropTypes.bool,
|
||||
// Array with column definition
|
||||
columns: PropTypes.array
|
||||
},
|
||||
static get propTypes() {
|
||||
return {
|
||||
// Custom tree decorator
|
||||
decorator: PropTypes.object,
|
||||
// True if the header should be visible
|
||||
header: PropTypes.bool,
|
||||
// Array with column definition
|
||||
columns: PropTypes.array
|
||||
};
|
||||
}
|
||||
|
||||
getDefaultProps: function () {
|
||||
static get defaultProps() {
|
||||
return {
|
||||
columns: [{
|
||||
id: "default"
|
||||
}]
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
getHeaderClass: function (colId) {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.getHeaderClass = this.getHeaderClass.bind(this);
|
||||
}
|
||||
|
||||
getHeaderClass(colId) {
|
||||
let decorator = this.props.decorator;
|
||||
if (!decorator || !decorator.getHeaderClass) {
|
||||
return [];
|
||||
@ -56,9 +58,9 @@ define(function (require, exports, module) {
|
||||
}
|
||||
|
||||
return classNames;
|
||||
},
|
||||
}
|
||||
|
||||
render: function () {
|
||||
render() {
|
||||
let cells = [];
|
||||
let visible = this.props.header;
|
||||
|
||||
@ -97,7 +99,7 @@ define(function (require, exports, module) {
|
||||
}, cells))
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
module.exports = TreeHeader;
|
||||
|
@ -7,54 +7,56 @@
|
||||
|
||||
// Make this available to both AMD and CJS environments
|
||||
define(function (require, exports, module) {
|
||||
// ReactJS
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const ReactDOM = require("devtools/client/shared/vendor/react-dom");
|
||||
const { Component, createFactory, PropTypes } = React;
|
||||
const { findDOMNode } = require("devtools/client/shared/vendor/react-dom");
|
||||
const { tr } = React.DOM;
|
||||
|
||||
// Tree
|
||||
const TreeCell = React.createFactory(require("./TreeCell"));
|
||||
const LabelCell = React.createFactory(require("./LabelCell"));
|
||||
const TreeCell = createFactory(require("./TreeCell"));
|
||||
const LabelCell = createFactory(require("./LabelCell"));
|
||||
|
||||
// Scroll
|
||||
const { scrollIntoViewIfNeeded } = require("devtools/client/shared/scroll");
|
||||
|
||||
// Shortcuts
|
||||
const { tr } = React.DOM;
|
||||
const PropTypes = React.PropTypes;
|
||||
|
||||
/**
|
||||
* This template represents a node in TreeView component. It's rendered
|
||||
* using <tr> element (the entire tree is one big <table>).
|
||||
*/
|
||||
let TreeRow = React.createClass({
|
||||
displayName: "TreeRow",
|
||||
|
||||
class TreeRow extends Component {
|
||||
// See TreeView component for more details about the props and
|
||||
// the 'member' object.
|
||||
propTypes: {
|
||||
member: PropTypes.shape({
|
||||
object: PropTypes.obSject,
|
||||
name: PropTypes.sring,
|
||||
type: PropTypes.string.isRequired,
|
||||
rowClass: PropTypes.string.isRequired,
|
||||
level: PropTypes.number.isRequired,
|
||||
hasChildren: PropTypes.bool,
|
||||
value: PropTypes.any,
|
||||
open: PropTypes.bool.isRequired,
|
||||
path: PropTypes.string.isRequired,
|
||||
hidden: PropTypes.bool,
|
||||
selected: PropTypes.bool,
|
||||
}),
|
||||
decorator: PropTypes.object,
|
||||
renderCell: PropTypes.object,
|
||||
renderLabelCell: PropTypes.object,
|
||||
columns: PropTypes.array.isRequired,
|
||||
id: PropTypes.string.isRequired,
|
||||
provider: PropTypes.object.isRequired,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
onMouseOver: PropTypes.func,
|
||||
onMouseOut: PropTypes.func
|
||||
},
|
||||
static get propTypes() {
|
||||
return {
|
||||
member: PropTypes.shape({
|
||||
object: PropTypes.obSject,
|
||||
name: PropTypes.sring,
|
||||
type: PropTypes.string.isRequired,
|
||||
rowClass: PropTypes.string.isRequired,
|
||||
level: PropTypes.number.isRequired,
|
||||
hasChildren: PropTypes.bool,
|
||||
value: PropTypes.any,
|
||||
open: PropTypes.bool.isRequired,
|
||||
path: PropTypes.string.isRequired,
|
||||
hidden: PropTypes.bool,
|
||||
selected: PropTypes.bool,
|
||||
}),
|
||||
decorator: PropTypes.object,
|
||||
renderCell: PropTypes.object,
|
||||
renderLabelCell: PropTypes.object,
|
||||
columns: PropTypes.array.isRequired,
|
||||
id: PropTypes.string.isRequired,
|
||||
provider: PropTypes.object.isRequired,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
onMouseOver: PropTypes.func,
|
||||
onMouseOut: PropTypes.func
|
||||
};
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.getRowClass = this.getRowClass.bind(this);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
// I don't like accessing the underlying DOM elements directly,
|
||||
@ -64,16 +66,16 @@ define(function (require, exports, module) {
|
||||
// The important part is that DOM elements don't need to be
|
||||
// re-created when they should appear again.
|
||||
if (nextProps.member.hidden != this.props.member.hidden) {
|
||||
let row = ReactDOM.findDOMNode(this);
|
||||
let row = findDOMNode(this);
|
||||
row.classList.toggle("hidden");
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimize row rendering. If props are the same do not render.
|
||||
* This makes the rendering a lot faster!
|
||||
*/
|
||||
shouldComponentUpdate: function (nextProps) {
|
||||
shouldComponentUpdate(nextProps) {
|
||||
let props = ["name", "open", "value", "loading", "selected", "hasChildren"];
|
||||
for (let p in props) {
|
||||
if (nextProps.member[props[p]] != this.props.member[props[p]]) {
|
||||
@ -82,20 +84,20 @@ define(function (require, exports, module) {
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
}
|
||||
|
||||
componentDidUpdate: function () {
|
||||
componentDidUpdate() {
|
||||
if (this.props.member.selected) {
|
||||
let row = ReactDOM.findDOMNode(this);
|
||||
let row = findDOMNode(this);
|
||||
// Because this is called asynchronously, context window might be
|
||||
// already gone.
|
||||
if (row.ownerDocument.defaultView) {
|
||||
scrollIntoViewIfNeeded(row);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
getRowClass: function (object) {
|
||||
getRowClass(object) {
|
||||
let decorator = this.props.decorator;
|
||||
if (!decorator || !decorator.getRowClass) {
|
||||
return [];
|
||||
@ -112,9 +114,9 @@ define(function (require, exports, module) {
|
||||
}
|
||||
|
||||
return classNames;
|
||||
},
|
||||
}
|
||||
|
||||
render: function () {
|
||||
render() {
|
||||
let member = this.props.member;
|
||||
let decorator = this.props.decorator;
|
||||
let props = {
|
||||
@ -198,7 +200,7 @@ define(function (require, exports, module) {
|
||||
tr(props, cells)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
|
@ -7,17 +7,22 @@
|
||||
|
||||
// Make this available to both AMD and CJS environments
|
||||
define(function (require, exports, module) {
|
||||
// ReactJS
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const { cloneElement, Component, createFactory, DOM: dom, PropTypes } =
|
||||
require("devtools/client/shared/vendor/react");
|
||||
|
||||
// Reps
|
||||
const { ObjectProvider } = require("./ObjectProvider");
|
||||
const TreeRow = React.createFactory(require("./TreeRow"));
|
||||
const TreeHeader = React.createFactory(require("./TreeHeader"));
|
||||
const TreeRow = createFactory(require("./TreeRow"));
|
||||
const TreeHeader = createFactory(require("./TreeHeader"));
|
||||
|
||||
// Shortcuts
|
||||
const DOM = React.DOM;
|
||||
const PropTypes = React.PropTypes;
|
||||
const defaultProps = {
|
||||
object: null,
|
||||
renderRow: null,
|
||||
provider: ObjectProvider,
|
||||
expandedNodes: new Set(),
|
||||
expandableStrings: true,
|
||||
columns: []
|
||||
};
|
||||
|
||||
/**
|
||||
* This component represents a tree view with expandable/collapsible nodes.
|
||||
@ -53,88 +58,96 @@ define(function (require, exports, module) {
|
||||
* renderLabelCell: function(object);
|
||||
* }
|
||||
*/
|
||||
let TreeView = React.createClass({
|
||||
displayName: "TreeView",
|
||||
|
||||
class TreeView extends Component {
|
||||
// The only required property (not set by default) is the input data
|
||||
// object that is used to puputate the tree.
|
||||
propTypes: {
|
||||
// The input data object.
|
||||
object: PropTypes.any,
|
||||
className: PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
// Data provider (see also the interface above)
|
||||
provider: PropTypes.shape({
|
||||
getChildren: PropTypes.func,
|
||||
hasChildren: PropTypes.func,
|
||||
getLabel: PropTypes.func,
|
||||
getValue: PropTypes.func,
|
||||
getKey: PropTypes.func,
|
||||
getType: PropTypes.func,
|
||||
}).isRequired,
|
||||
// Tree decorator (see also the interface above)
|
||||
decorator: PropTypes.shape({
|
||||
getRowClass: PropTypes.func,
|
||||
getCellClass: PropTypes.func,
|
||||
getHeaderClass: PropTypes.func,
|
||||
renderValue: PropTypes.func,
|
||||
static get propTypes() {
|
||||
return {
|
||||
// The input data object.
|
||||
object: PropTypes.any,
|
||||
className: PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
// Data provider (see also the interface above)
|
||||
provider: PropTypes.shape({
|
||||
getChildren: PropTypes.func,
|
||||
hasChildren: PropTypes.func,
|
||||
getLabel: PropTypes.func,
|
||||
getValue: PropTypes.func,
|
||||
getKey: PropTypes.func,
|
||||
getType: PropTypes.func,
|
||||
}).isRequired,
|
||||
// Tree decorator (see also the interface above)
|
||||
decorator: PropTypes.shape({
|
||||
getRowClass: PropTypes.func,
|
||||
getCellClass: PropTypes.func,
|
||||
getHeaderClass: PropTypes.func,
|
||||
renderValue: PropTypes.func,
|
||||
renderRow: PropTypes.func,
|
||||
renderCell: PropTypes.func,
|
||||
renderLabelCell: PropTypes.func,
|
||||
}),
|
||||
// Custom tree row (node) renderer
|
||||
renderRow: PropTypes.func,
|
||||
// Custom cell renderer
|
||||
renderCell: PropTypes.func,
|
||||
// Custom value renderef
|
||||
renderValue: PropTypes.func,
|
||||
// Custom tree label (including a toggle button) renderer
|
||||
renderLabelCell: PropTypes.func,
|
||||
}),
|
||||
// Custom tree row (node) renderer
|
||||
renderRow: PropTypes.func,
|
||||
// Custom cell renderer
|
||||
renderCell: PropTypes.func,
|
||||
// Custom value renderef
|
||||
renderValue: PropTypes.func,
|
||||
// Custom tree label (including a toggle button) renderer
|
||||
renderLabelCell: PropTypes.func,
|
||||
// Set of expanded nodes
|
||||
expandedNodes: PropTypes.object,
|
||||
// Custom filtering callback
|
||||
onFilter: PropTypes.func,
|
||||
// Custom sorting callback
|
||||
onSort: PropTypes.func,
|
||||
// A header is displayed if set to true
|
||||
header: PropTypes.bool,
|
||||
// Long string is expandable by a toggle button
|
||||
expandableStrings: PropTypes.bool,
|
||||
// Array of columns
|
||||
columns: PropTypes.arrayOf(PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
title: PropTypes.string,
|
||||
width: PropTypes.string
|
||||
}))
|
||||
},
|
||||
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
object: null,
|
||||
renderRow: null,
|
||||
provider: ObjectProvider,
|
||||
expandedNodes: new Set(),
|
||||
expandableStrings: true,
|
||||
columns: []
|
||||
// Set of expanded nodes
|
||||
expandedNodes: PropTypes.object,
|
||||
// Custom filtering callback
|
||||
onFilter: PropTypes.func,
|
||||
// Custom sorting callback
|
||||
onSort: PropTypes.func,
|
||||
// A header is displayed if set to true
|
||||
header: PropTypes.bool,
|
||||
// Long string is expandable by a toggle button
|
||||
expandableStrings: PropTypes.bool,
|
||||
// Array of columns
|
||||
columns: PropTypes.arrayOf(PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
title: PropTypes.string,
|
||||
width: PropTypes.string
|
||||
}))
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
getInitialState: function () {
|
||||
return {
|
||||
expandedNodes: this.props.expandedNodes,
|
||||
columns: ensureDefaultColumn(this.props.columns),
|
||||
static get defaultProps() {
|
||||
return defaultProps;
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
expandedNodes: props.expandedNodes,
|
||||
columns: ensureDefaultColumn(props.columns),
|
||||
selected: null
|
||||
};
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function (nextProps) {
|
||||
this.toggle = this.toggle.bind(this);
|
||||
this.isExpanded = this.isExpanded.bind(this);
|
||||
this.onKeyDown = this.onKeyDown.bind(this);
|
||||
this.onKeyUp = this.onKeyUp.bind(this);
|
||||
this.onClickRow = this.onClickRow.bind(this);
|
||||
this.getSelectedRow = this.getSelectedRow.bind(this);
|
||||
this.selectRow = this.selectRow.bind(this);
|
||||
this.isSelected = this.isSelected.bind(this);
|
||||
this.onFilter = this.onFilter.bind(this);
|
||||
this.onSort = this.onSort.bind(this);
|
||||
this.getMembers = this.getMembers.bind(this);
|
||||
this.renderRows = this.renderRows.bind(this);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
let { expandedNodes } = nextProps;
|
||||
this.setState(Object.assign({}, this.state, {
|
||||
expandedNodes,
|
||||
}));
|
||||
},
|
||||
}
|
||||
|
||||
componentDidUpdate: function () {
|
||||
componentDidUpdate() {
|
||||
let selected = this.getSelectedRow(this.rows);
|
||||
if (!selected && this.rows.length > 0) {
|
||||
// TODO: Do better than just selecting the first row again. We want to
|
||||
@ -142,11 +155,58 @@ define(function (require, exports, module) {
|
||||
// row is removed.
|
||||
this.selectRow(this.rows[0].props.member.path);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
static subPath(path, subKey) {
|
||||
return path + "/" + String(subKey).replace(/[\\/]/g, "\\$&");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a set with the paths of the nodes that should be expanded by default
|
||||
* according to the passed options.
|
||||
* @param {Object} The root node of the tree.
|
||||
* @param {Object} [optional] An object with the following optional parameters:
|
||||
* - maxLevel: nodes nested deeper than this level won't be expanded.
|
||||
* - maxNodes: maximum number of nodes that can be expanded. The traversal is
|
||||
breadth-first, so expanding nodes nearer to the root will be preferred.
|
||||
Sibling nodes will either be all expanded or none expanded.
|
||||
* }
|
||||
*/
|
||||
static getExpandedNodes(rootObj, { maxLevel = Infinity, maxNodes = Infinity } = {}) {
|
||||
let expandedNodes = new Set();
|
||||
let queue = [{
|
||||
object: rootObj,
|
||||
level: 1,
|
||||
path: ""
|
||||
}];
|
||||
while (queue.length) {
|
||||
let {object, level, path} = queue.shift();
|
||||
if (Object(object) !== object) {
|
||||
continue;
|
||||
}
|
||||
let keys = Object.keys(object);
|
||||
if (expandedNodes.size + keys.length > maxNodes) {
|
||||
// Avoid having children half expanded.
|
||||
break;
|
||||
}
|
||||
for (let key of keys) {
|
||||
let nodePath = TreeView.subPath(path, key);
|
||||
expandedNodes.add(nodePath);
|
||||
if (level < maxLevel) {
|
||||
queue.push({
|
||||
object: object[key],
|
||||
level: level + 1,
|
||||
path: nodePath
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return expandedNodes;
|
||||
}
|
||||
|
||||
// Node expand/collapse
|
||||
|
||||
toggle: function (nodePath) {
|
||||
toggle(nodePath) {
|
||||
let nodes = this.state.expandedNodes;
|
||||
if (this.isExpanded(nodePath)) {
|
||||
nodes.delete(nodePath);
|
||||
@ -158,22 +218,22 @@ define(function (require, exports, module) {
|
||||
this.setState(Object.assign({}, this.state, {
|
||||
expandedNodes: nodes
|
||||
}));
|
||||
},
|
||||
}
|
||||
|
||||
isExpanded: function (nodePath) {
|
||||
isExpanded(nodePath) {
|
||||
return this.state.expandedNodes.has(nodePath);
|
||||
},
|
||||
}
|
||||
|
||||
// Event Handlers
|
||||
|
||||
onKeyDown: function (event) {
|
||||
onKeyDown(event) {
|
||||
if (["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(
|
||||
event.key)) {
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
onKeyUp: function (event) {
|
||||
onKeyUp(event) {
|
||||
let row = this.getSelectedRow(this.rows);
|
||||
if (!row) {
|
||||
return;
|
||||
@ -209,33 +269,33 @@ define(function (require, exports, module) {
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
},
|
||||
}
|
||||
|
||||
onClickRow: function (nodePath, event) {
|
||||
onClickRow(nodePath, event) {
|
||||
event.stopPropagation();
|
||||
let cell = event.target.closest("td");
|
||||
if (cell && cell.classList.contains("treeLabelCell")) {
|
||||
this.toggle(nodePath);
|
||||
}
|
||||
this.selectRow(nodePath);
|
||||
},
|
||||
}
|
||||
|
||||
getSelectedRow: function (rows) {
|
||||
getSelectedRow(rows) {
|
||||
if (!this.state.selected || rows.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return rows.find(row => this.isSelected(row.props.member.path));
|
||||
},
|
||||
}
|
||||
|
||||
selectRow: function (nodePath) {
|
||||
selectRow(nodePath) {
|
||||
this.setState(Object.assign({}, this.state, {
|
||||
selected: nodePath
|
||||
}));
|
||||
},
|
||||
}
|
||||
|
||||
isSelected: function (nodePath) {
|
||||
isSelected(nodePath) {
|
||||
return nodePath === this.state.selected;
|
||||
},
|
||||
}
|
||||
|
||||
// Filtering & Sorting
|
||||
|
||||
@ -243,15 +303,15 @@ define(function (require, exports, module) {
|
||||
* Filter out nodes that don't correspond to the current filter.
|
||||
* @return {Boolean} true if the node should be visible otherwise false.
|
||||
*/
|
||||
onFilter: function (object) {
|
||||
onFilter(object) {
|
||||
let onFilter = this.props.onFilter;
|
||||
return onFilter ? onFilter(object) : true;
|
||||
},
|
||||
}
|
||||
|
||||
onSort: function (parent, children) {
|
||||
onSort(parent, children) {
|
||||
let onSort = this.props.onSort;
|
||||
return onSort ? onSort(parent, children) : children;
|
||||
},
|
||||
}
|
||||
|
||||
// Members
|
||||
|
||||
@ -259,7 +319,7 @@ define(function (require, exports, module) {
|
||||
* Return children node objects (so called 'members') for given
|
||||
* parent object.
|
||||
*/
|
||||
getMembers: function (parent, level, path) {
|
||||
getMembers(parent, level, path) {
|
||||
// Strings don't have children. Note that 'long' strings are using
|
||||
// the expander icon (+/-) to display the entire original value,
|
||||
// but there are no child items.
|
||||
@ -320,12 +380,12 @@ define(function (require, exports, module) {
|
||||
selected: this.isSelected(nodePath)
|
||||
};
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Render tree rows/nodes.
|
||||
*/
|
||||
renderRows: function (parent, level = 0, path = "") {
|
||||
renderRows(parent, level = 0, path = "") {
|
||||
let rows = [];
|
||||
let decorator = this.props.decorator;
|
||||
let renderRow = this.props.renderRow || TreeRow;
|
||||
@ -367,7 +427,7 @@ define(function (require, exports, module) {
|
||||
if (!Array.isArray(childRows)) {
|
||||
let lastIndex = rows.length - 1;
|
||||
props.member.loading = true;
|
||||
rows[lastIndex] = React.cloneElement(rows[lastIndex], props);
|
||||
rows[lastIndex] = cloneElement(rows[lastIndex], props);
|
||||
} else {
|
||||
rows = rows.concat(childRows);
|
||||
}
|
||||
@ -375,9 +435,9 @@ define(function (require, exports, module) {
|
||||
});
|
||||
|
||||
return rows;
|
||||
},
|
||||
}
|
||||
|
||||
render: function () {
|
||||
render() {
|
||||
let root = this.props.object;
|
||||
let classNames = ["treeTable"];
|
||||
this.rows = [];
|
||||
@ -403,7 +463,7 @@ define(function (require, exports, module) {
|
||||
});
|
||||
|
||||
return (
|
||||
DOM.table({
|
||||
dom.table({
|
||||
className: classNames.join(" "),
|
||||
role: "tree",
|
||||
tabIndex: 0,
|
||||
@ -414,62 +474,13 @@ define(function (require, exports, module) {
|
||||
cellPadding: 0,
|
||||
cellSpacing: 0},
|
||||
TreeHeader(props),
|
||||
DOM.tbody({
|
||||
dom.tbody({
|
||||
role: "presentation"
|
||||
}, rows)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
TreeView.subPath = function (path, subKey) {
|
||||
return path + "/" + String(subKey).replace(/[\\/]/g, "\\$&");
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a set with the paths of the nodes that should be expanded by default
|
||||
* according to the passed options.
|
||||
* @param {Object} The root node of the tree.
|
||||
* @param {Object} [optional] An object with the following optional parameters:
|
||||
* - maxLevel: nodes nested deeper than this level won't be expanded.
|
||||
* - maxNodes: maximum number of nodes that can be expanded. The traversal is
|
||||
breadth-first, so expanding nodes nearer to the root will be preferred.
|
||||
Sibling nodes will either be all expanded or none expanded.
|
||||
* }
|
||||
*/
|
||||
TreeView.getExpandedNodes = function (rootObj,
|
||||
{ maxLevel = Infinity, maxNodes = Infinity } = {}
|
||||
) {
|
||||
let expandedNodes = new Set();
|
||||
let queue = [{
|
||||
object: rootObj,
|
||||
level: 1,
|
||||
path: ""
|
||||
}];
|
||||
while (queue.length) {
|
||||
let {object, level, path} = queue.shift();
|
||||
if (Object(object) !== object) {
|
||||
continue;
|
||||
}
|
||||
let keys = Object.keys(object);
|
||||
if (expandedNodes.size + keys.length > maxNodes) {
|
||||
// Avoid having children half expanded.
|
||||
break;
|
||||
}
|
||||
for (let key of keys) {
|
||||
let nodePath = TreeView.subPath(path, key);
|
||||
expandedNodes.add(nodePath);
|
||||
if (level < maxLevel) {
|
||||
queue.push({
|
||||
object: object[key],
|
||||
level: level + 1,
|
||||
path: nodePath
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return expandedNodes;
|
||||
};
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user