285 lines
7.7 KiB
JavaScript
285 lines
7.7 KiB
JavaScript
/* 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.mjs");
|
|
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);
|