Source code
Revision control
Copy as Markdown
Other Tools
/* 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
"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);