/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; // React & Redux const { Component, } = require("resource://devtools/client/shared/vendor/react.js"); const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); const { connect, } = require("resource://devtools/client/shared/vendor/react-redux.js"); const { getReverseSearchTotalResults, getReverseSearchResultPosition, getReverseSearchResult, } = require("resource://devtools/client/webconsole/selectors/history.js"); loader.lazyRequireGetter( this, "PropTypes", "resource://devtools/client/shared/vendor/react-prop-types.js" ); loader.lazyRequireGetter( this, "actions", "resource://devtools/client/webconsole/actions/index.js" ); loader.lazyRequireGetter( this, "l10n", "resource://devtools/client/webconsole/utils/messages.js", true ); loader.lazyRequireGetter( this, "PluralForm", "resource://devtools/shared/plural-form.js", true ); loader.lazyRequireGetter( this, "KeyCodes", "resource://devtools/client/shared/keycodes.js", true ); const isMacOS = Services.appinfo.OS === "Darwin"; class ReverseSearchInput extends Component { static get propTypes() { return { dispatch: PropTypes.func.isRequired, setInputValue: PropTypes.func.isRequired, focusInput: PropTypes.func.isRequired, reverseSearchResult: PropTypes.string, reverseSearchTotalResults: PropTypes.number, reverseSearchResultPosition: PropTypes.number, visible: PropTypes.bool, initialValue: PropTypes.string, }; } constructor(props) { super(props); this.onInputKeyDown = this.onInputKeyDown.bind(this); } componentDidUpdate(prevProps) { const { setInputValue, focusInput } = this.props; if ( prevProps.reverseSearchResult !== this.props.reverseSearchResult && this.props.visible && this.props.reverseSearchTotalResults > 0 ) { setInputValue(this.props.reverseSearchResult); } if (prevProps.visible === true && this.props.visible === false) { focusInput(); } if ( prevProps.visible === false && this.props.visible === true && this.props.initialValue ) { this.inputNode.value = this.props.initialValue; } } onEnterKeyboardShortcut(event) { const { dispatch } = this.props; event.stopPropagation(); dispatch(actions.reverseSearchInputToggle()); dispatch(actions.evaluateExpression(undefined, "reverse-search")); } onEscapeKeyboardShortcut(event) { const { dispatch } = this.props; event.stopPropagation(); dispatch(actions.reverseSearchInputToggle()); } onBackwardNavigationKeyBoardShortcut(event, canNavigate) { const { dispatch } = this.props; event.stopPropagation(); event.preventDefault(); if (canNavigate) { dispatch(actions.showReverseSearchBack({ access: "keyboard" })); } } onForwardNavigationKeyBoardShortcut(event, canNavigate) { const { dispatch } = this.props; event.stopPropagation(); event.preventDefault(); if (canNavigate) { dispatch(actions.showReverseSearchNext({ access: "keyboard" })); } } onInputKeyDown(event) { const { keyCode, key, ctrlKey, shiftKey } = event; const { reverseSearchTotalResults } = this.props; // On Enter, we trigger an execute. if (keyCode === KeyCodes.DOM_VK_RETURN) { return this.onEnterKeyboardShortcut(event); } const lowerCaseKey = key.toLowerCase(); // On Escape (and Ctrl + c on OSX), we close the reverse search input. if ( keyCode === KeyCodes.DOM_VK_ESCAPE || (isMacOS && ctrlKey && lowerCaseKey === "c") ) { return this.onEscapeKeyboardShortcut(event); } const canNavigate = Number.isInteger(reverseSearchTotalResults) && reverseSearchTotalResults > 1; if ( (!isMacOS && key === "F9" && !shiftKey) || (isMacOS && ctrlKey && lowerCaseKey === "r") ) { return this.onBackwardNavigationKeyBoardShortcut(event, canNavigate); } if ( (!isMacOS && key === "F9" && shiftKey) || (isMacOS && ctrlKey && lowerCaseKey === "s") ) { return this.onForwardNavigationKeyBoardShortcut(event, canNavigate); } return null; } renderSearchInformation() { const { reverseSearchTotalResults, reverseSearchResultPosition } = this.props; if (!Number.isInteger(reverseSearchTotalResults)) { return null; } let text; if (reverseSearchTotalResults === 0) { text = l10n.getStr("webconsole.reverseSearch.noResult"); } else { const resultsString = l10n.getStr("webconsole.reverseSearch.results"); text = PluralForm.get(reverseSearchTotalResults, resultsString) .replace("#1", reverseSearchResultPosition) .replace("#2", reverseSearchTotalResults); } return dom.div({ className: "reverse-search-info" }, text); } renderNavigationButtons() { const { dispatch, reverseSearchTotalResults } = this.props; if ( !Number.isInteger(reverseSearchTotalResults) || reverseSearchTotalResults <= 1 ) { return null; } return [ dom.button({ key: "search-result-button-prev", className: "devtools-button search-result-button-prev", title: l10n.getFormatStr( "webconsole.reverseSearch.result.previousButton.tooltip", [isMacOS ? "Ctrl + R" : "F9"] ), onClick: () => { dispatch(actions.showReverseSearchBack({ access: "click" })); this.inputNode.focus(); }, }), dom.button({ key: "search-result-button-next", className: "devtools-button search-result-button-next", title: l10n.getFormatStr( "webconsole.reverseSearch.result.nextButton.tooltip", [isMacOS ? "Ctrl + S" : "Shift + F9"] ), onClick: () => { dispatch(actions.showReverseSearchNext({ access: "click" })); this.inputNode.focus(); }, }), ]; } render() { const { dispatch, visible, reverseSearchTotalResults } = this.props; if (!visible) { return null; } const classNames = ["reverse-search"]; if (reverseSearchTotalResults === 0) { classNames.push("no-result"); } return dom.div( { className: classNames.join(" ") }, dom.input({ ref: node => { this.inputNode = node; }, autoFocus: true, placeholder: l10n.getStr("webconsole.reverseSearch.input.placeHolder"), className: "reverse-search-input devtools-monospace", onKeyDown: this.onInputKeyDown, onInput: ({ target }) => dispatch(actions.reverseSearchInputChange(target.value)), }), dom.div( { className: "reverse-search-actions", }, this.renderSearchInformation(), this.renderNavigationButtons(), dom.button({ className: "devtools-button reverse-search-close-button", title: l10n.getFormatStr( "webconsole.reverseSearch.closeButton.tooltip", ["Esc" + (isMacOS ? " | Ctrl + C" : "")] ), onClick: () => { dispatch(actions.reverseSearchInputToggle()); }, }) ) ); } } const mapStateToProps = state => ({ visible: state.ui.reverseSearchInputVisible, reverseSearchTotalResults: getReverseSearchTotalResults(state), reverseSearchResultPosition: getReverseSearchResultPosition(state), reverseSearchResult: getReverseSearchResult(state), }); const mapDispatchToProps = dispatch => ({ dispatch }); module.exports = connect( mapStateToProps, mapDispatchToProps )(ReverseSearchInput);