diff options
Diffstat (limited to 'devtools/client/debugger/src/components/SecondaryPanes/EventListeners.js')
-rw-r--r-- | devtools/client/debugger/src/components/SecondaryPanes/EventListeners.js | 339 |
1 files changed, 339 insertions, 0 deletions
diff --git a/devtools/client/debugger/src/components/SecondaryPanes/EventListeners.js b/devtools/client/debugger/src/components/SecondaryPanes/EventListeners.js new file mode 100644 index 0000000000..ce7eabf89d --- /dev/null +++ b/devtools/client/debugger/src/components/SecondaryPanes/EventListeners.js @@ -0,0 +1,339 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */ + +import React, { Component } from "devtools/client/shared/vendor/react"; +import { + div, + input, + li, + ul, + span, + button, + form, + label, +} from "devtools/client/shared/vendor/react-dom-factories"; +import PropTypes from "devtools/client/shared/vendor/react-prop-types"; + +import { connect } from "devtools/client/shared/vendor/react-redux"; +import actions from "../../actions/index"; +import { + getActiveEventListeners, + getEventListenerBreakpointTypes, + getEventListenerExpanded, +} from "../../selectors/index"; + +import AccessibleImage from "../shared/AccessibleImage"; + +const classnames = require("resource://devtools/client/shared/classnames.js"); + +class EventListeners extends Component { + state = { + searchText: "", + focused: false, + }; + + static get propTypes() { + return { + activeEventListeners: PropTypes.array.isRequired, + addEventListenerExpanded: PropTypes.func.isRequired, + addEventListeners: PropTypes.func.isRequired, + categories: PropTypes.array.isRequired, + expandedCategories: PropTypes.array.isRequired, + removeEventListenerExpanded: PropTypes.func.isRequired, + removeEventListeners: PropTypes.func.isRequired, + }; + } + + hasMatch(eventOrCategoryName, searchText) { + const lowercaseEventOrCategoryName = eventOrCategoryName.toLowerCase(); + const lowercaseSearchText = searchText.toLowerCase(); + + return lowercaseEventOrCategoryName.includes(lowercaseSearchText); + } + + getSearchResults() { + const { searchText } = this.state; + const { categories } = this.props; + const searchResults = categories.reduce((results, cat, index) => { + const category = categories[index]; + + if (this.hasMatch(category.name, searchText)) { + results[category.name] = category.events; + } else { + results[category.name] = category.events.filter(event => + this.hasMatch(event.name, searchText) + ); + } + + return results; + }, {}); + + return searchResults; + } + + onCategoryToggle(category) { + const { + expandedCategories, + removeEventListenerExpanded, + addEventListenerExpanded, + } = this.props; + + if (expandedCategories.includes(category)) { + removeEventListenerExpanded(category); + } else { + addEventListenerExpanded(category); + } + } + + onCategoryClick(category, isChecked) { + const { addEventListeners, removeEventListeners } = this.props; + const eventsIds = category.events.map(event => event.id); + + if (isChecked) { + addEventListeners(eventsIds); + } else { + removeEventListeners(eventsIds); + } + } + + onEventTypeClick(eventId, isChecked) { + const { addEventListeners, removeEventListeners } = this.props; + if (isChecked) { + addEventListeners([eventId]); + } else { + removeEventListeners([eventId]); + } + } + + onInputChange = event => { + this.setState({ searchText: event.currentTarget.value }); + }; + + onKeyDown = event => { + if (event.key === "Escape") { + this.setState({ searchText: "" }); + } + }; + + onFocus = event => { + this.setState({ focused: true }); + }; + + onBlur = event => { + this.setState({ focused: false }); + }; + + renderSearchInput() { + const { focused, searchText } = this.state; + const placeholder = L10N.getStr("eventListenersHeader1.placeholder"); + return form( + { + className: "event-search-form", + onSubmit: e => e.preventDefault(), + }, + input({ + className: classnames("event-search-input", { + focused, + }), + placeholder: placeholder, + value: searchText, + onChange: this.onInputChange, + onKeyDown: this.onKeyDown, + onFocus: this.onFocus, + onBlur: this.onBlur, + }) + ); + } + + renderClearSearchButton() { + const { searchText } = this.state; + + if (!searchText) { + return null; + } + return button({ + onClick: () => + this.setState({ + searchText: "", + }), + className: "devtools-searchinput-clear", + }); + } + + renderCategoriesList() { + const { categories } = this.props; + return ul( + { + className: "event-listeners-list", + }, + categories.map((category, index) => { + return li( + { + className: "event-listener-group", + key: index, + }, + this.renderCategoryHeading(category), + this.renderCategoryListing(category) + ); + }) + ); + } + + renderSearchResultsList() { + const searchResults = this.getSearchResults(); + return ul( + { + className: "event-search-results-list", + }, + Object.keys(searchResults).map(category => { + return searchResults[category].map(event => { + return this.renderListenerEvent(event, category); + }); + }) + ); + } + + renderCategoryHeading(category) { + const { activeEventListeners, expandedCategories } = this.props; + const { events } = category; + + const expanded = expandedCategories.includes(category.name); + const checked = events.every(({ id }) => activeEventListeners.includes(id)); + const indeterminate = + !checked && events.some(({ id }) => activeEventListeners.includes(id)); + + return div( + { + className: "event-listener-header", + }, + button( + { + className: "event-listener-expand", + onClick: () => this.onCategoryToggle(category.name), + }, + React.createElement(AccessibleImage, { + className: classnames("arrow", { + expanded, + }), + }) + ), + label( + { + className: "event-listener-label", + }, + input({ + type: "checkbox", + value: category.name, + onChange: e => { + this.onCategoryClick( + category, + // Clicking an indeterminate checkbox should always have the + // effect of disabling any selected items. + indeterminate ? false : e.target.checked + ); + }, + checked: checked, + ref: el => el && (el.indeterminate = indeterminate), + }), + span( + { + className: "event-listener-category", + }, + category.name + ) + ) + ); + } + + renderCategoryListing(category) { + const { expandedCategories } = this.props; + + const expanded = expandedCategories.includes(category.name); + if (!expanded) { + return null; + } + return ul( + null, + category.events.map(event => { + return this.renderListenerEvent(event, category.name); + }) + ); + } + + renderCategory(category) { + return span( + { + className: "category-label", + }, + category, + " \u25B8 " + ); + } + + renderListenerEvent(event, category) { + const { activeEventListeners } = this.props; + const { searchText } = this.state; + return li( + { + className: "event-listener-event", + key: event.id, + }, + label( + { + className: "event-listener-label", + }, + input({ + type: "checkbox", + value: event.id, + onChange: e => this.onEventTypeClick(event.id, e.target.checked), + checked: activeEventListeners.includes(event.id), + }), + span( + { + className: "event-listener-name", + }, + searchText ? this.renderCategory(category) : null, + event.name + ) + ) + ); + } + + render() { + const { searchText } = this.state; + return div( + { + className: "event-listeners", + }, + div( + { + className: "event-search-container", + }, + this.renderSearchInput(), + this.renderClearSearchButton() + ), + div( + { + className: "event-listeners-content", + }, + searchText + ? this.renderSearchResultsList() + : this.renderCategoriesList() + ) + ); + } +} + +const mapStateToProps = state => ({ + activeEventListeners: getActiveEventListeners(state), + categories: getEventListenerBreakpointTypes(state), + expandedCategories: getEventListenerExpanded(state), +}); + +export default connect(mapStateToProps, { + addEventListeners: actions.addEventListenerBreakpoints, + removeEventListeners: actions.removeEventListenerBreakpoints, + addEventListenerExpanded: actions.addEventListenerExpanded, + removeEventListenerExpanded: actions.removeEventListenerExpanded, +})(EventListeners); |