summaryrefslogtreecommitdiffstats
path: root/devtools/client/webconsole/components/Input/ReverseSearchInput.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/webconsole/components/Input/ReverseSearchInput.js')
-rw-r--r--devtools/client/webconsole/components/Input/ReverseSearchInput.js285
1 files changed, 285 insertions, 0 deletions
diff --git a/devtools/client/webconsole/components/Input/ReverseSearchInput.js b/devtools/client/webconsole/components/Input/ReverseSearchInput.js
new file mode 100644
index 0000000000..5cece45bc7
--- /dev/null
+++ b/devtools/client/webconsole/components/Input/ReverseSearchInput.js
@@ -0,0 +1,285 @@
+/* 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);