Bug 1354504 - Add autocomplete to network monitor search box. r=jdescottes, ntim

MozReview-Commit-ID: KojxbqOAJAQ
This commit is contained in:
Ruturaj K. Vartak 2017-05-11 04:25:00 +01:00
Родитель 652857ca5b
Коммит eeb5e9e9e1
8 изменённых файлов: 266 добавлений и 70 удалений

Просмотреть файл

@ -12,7 +12,7 @@ const {
} = require("devtools/client/shared/vendor/react");
const { connect } = require("devtools/client/shared/vendor/react-redux");
const Actions = require("../actions/index");
const { FILTER_SEARCH_DELAY } = require("../constants");
const { FILTER_SEARCH_DELAY, FILTER_FLAGS } = require("../constants");
const {
getDisplayedRequestsSummary,
getRequestFilterTypes,
@ -109,6 +109,7 @@ const Toolbar = createClass({
placeholder: SEARCH_PLACE_HOLDER,
type: "filter",
onChange: setRequestFilterText,
autocompleteList: FILTER_FLAGS.map((item) => `${item}:`),
}),
button({
className: toggleButtonClassName.join(" "),

Просмотреть файл

@ -158,6 +158,22 @@ const HEADERS = [
}
];
const HEADER_FILTERS = HEADERS
.filter(h => h.canFilter)
.map(h => h.filterKey || h.name);
const FILTER_FLAGS = [
...HEADER_FILTERS,
"set-cookie-domain",
"set-cookie-name",
"set-cookie-value",
"mime-type",
"larger-than",
"is",
"has-response-header",
"regexp",
];
const REQUESTS_WATERFALL = {
BACKGROUND_TICKS_MULTIPLE: 5, // ms
BACKGROUND_TICKS_SCALES: 3,
@ -180,6 +196,7 @@ const general = {
EVENTS,
FILTER_SEARCH_DELAY: 200,
HEADERS,
FILTER_FLAGS,
SOURCE_EDITOR_SYNTAX_HIGHLIGHT_MAX_SIZE: 51200, // 50 KB in bytes
REQUESTS_WATERFALL,
};

Просмотреть файл

@ -30,23 +30,8 @@
"use strict";
const { HEADERS } = require("../constants");
const { FILTER_FLAGS } = require("../constants");
const { getFormattedIPAndPort } = require("./format-utils");
const HEADER_FILTERS = HEADERS
.filter(h => h.canFilter)
.map(h => h.filterKey || h.name);
const FILTER_FLAGS = [
...HEADER_FILTERS,
"set-cookie-domain",
"set-cookie-name",
"set-cookie-value",
"mime-type",
"larger-than",
"is",
"has-response-header",
"regexp",
];
/*
The function `parseFilters` is from:

Просмотреть файл

@ -42,6 +42,7 @@ let webpackConfig = {
"devtools/client/framework/menu": "devtools-modules/src/menu",
"devtools/client/framework/menu-item": path.join(__dirname, "../../client/framework/menu-item"),
"devtools/client/locales": path.join(__dirname, "../../client/locales/en-US"),
"devtools/client/shared/components/autocomplete-popup": path.join(__dirname, "../../client/shared/components/autocomplete-popup"),
"devtools/client/shared/components/reps/reps": path.join(__dirname, "../../client/shared/components/reps/reps"),
"devtools/client/shared/components/search-box": path.join(__dirname, "../../client/shared/components/search-box"),
"devtools/client/shared/components/splitter/draggable": path.join(__dirname, "../../client/shared/components/splitter/draggable"),

Просмотреть файл

@ -0,0 +1,122 @@
/* 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";
const { DOM: dom, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
module.exports = createClass({
displayName: "AutocompletePopup",
propTypes: {
list: PropTypes.array.isRequired,
filter: PropTypes.string.isRequired,
onItemSelected: PropTypes.func.isRequired,
},
getInitialState() {
return this.computeState(this.props);
},
componentWillReceiveProps(nextProps) {
if (this.props.filter === nextProps.filter) {
return;
}
this.setState(this.computeState(nextProps));
},
componentDidUpdate() {
if (this.refs.selected) {
this.refs.selected.scrollIntoView(false);
}
},
computeState({ filter, list }) {
let filteredList = list.filter((item) => {
return item.toLowerCase().startsWith(filter.toLowerCase())
&& item.toLowerCase() !== filter.toLowerCase();
}).sort();
let selectedIndex = filteredList.length == 1 ? 0 : -1;
return { filteredList, selectedIndex };
},
/**
* Use this method to select the top-most item
* This method is public, called outside of the autocomplete-popup component.
*/
jumpToTop() {
this.setState({ selectedIndex: 0 });
},
/**
* Use this method to select the bottom-most item
* This method is public.
*/
jumpToBottom() {
let selectedIndex = this.state.filteredList.length - 1;
this.setState({ selectedIndex });
},
/**
* Increment the selected index with the provided increment value. Will cycle to the
* beginning/end of the list if the index exceeds the list boundaries.
* This method is public.
*
* @param {number} increment - No. of hops in the direction
*/
jumpBy(increment = 1) {
let { filteredList, selectedIndex } = this.state;
let nextIndex = selectedIndex + increment;
if (increment > 0) {
// Positive cycling
nextIndex = nextIndex > filteredList.length - 1 ? 0 : nextIndex;
} else if (increment < 0) {
// Inverse cycling
nextIndex = nextIndex < 0 ? filteredList.length - 1 : nextIndex;
}
this.setState({selectedIndex: nextIndex});
},
/**
* Submit the currently selected item to the onItemSelected callback
* This method is public.
*/
select() {
if (this.refs.selected) {
this.props.onItemSelected(this.refs.selected.textContent);
}
},
onMouseDown(e) {
e.preventDefault();
this.setState({ selectedIndex: Number(e.target.dataset.index) }, this.select);
},
render() {
let { filteredList } = this.state;
return filteredList.length > 0 && dom.div(
{ className: "devtools-autocomplete-popup devtools-monospace" },
dom.ul(
{ className: "devtools-autocomplete-listbox" },
filteredList.map((item, i) => {
let isSelected = this.state.selectedIndex == i;
let itemClassList = ["autocomplete-item"];
if (isSelected) {
itemClassList.push("autocomplete-selected");
}
return dom.li({
key: i,
"data-index": i,
className: itemClassList.join(" "),
ref: isSelected ? "selected" : null,
onMouseDown: this.onMouseDown,
}, item);
})
)
);
}
});

Просмотреть файл

@ -12,6 +12,7 @@ DIRS += [
]
DevToolsModules(
'autocomplete-popup.js',
'frame.js',
'h-split-box.js',
'notification-box.css',

Просмотреть файл

@ -6,8 +6,9 @@
"use strict";
const { DOM: dom, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
const { DOM: dom, createClass, PropTypes, createFactory } = require("devtools/client/shared/vendor/react");
const KeyShortcuts = require("devtools/client/shared/key-shortcuts");
const AutocompletePopup = createFactory(require("devtools/client/shared/components/autocomplete-popup"));
/**
* A generic search box component for use across devtools
@ -20,12 +21,20 @@ module.exports = createClass({
keyShortcut: PropTypes.string,
onChange: PropTypes.func,
placeholder: PropTypes.string,
type: PropTypes.string
type: PropTypes.string,
autocompleteList: PropTypes.array,
},
getDefaultProps() {
return {
autocompleteList: [],
};
},
getInitialState() {
return {
value: ""
value: "",
focused: false,
};
},
@ -56,7 +65,9 @@ module.exports = createClass({
onChange() {
if (this.state.value !== this.refs.input.value) {
this.setState({ value: this.refs.input.value });
this.setState({
value: this.refs.input.value,
});
}
if (!this.props.delay) {
@ -82,8 +93,59 @@ module.exports = createClass({
this.onChange();
},
onFocus() {
this.setState({ focused: true });
},
onBlur() {
this.setState({ focused: false });
},
onKeyDown(e) {
let { autocompleteList } = this.props;
let { autocomplete } = this.refs;
if (autocompleteList.length == 0) {
return;
}
switch (e.key) {
case "ArrowDown":
autocomplete.jumpBy(1);
break;
case "ArrowUp":
autocomplete.jumpBy(-1);
break;
case "PageDown":
autocomplete.jumpBy(5);
break;
case "PageUp":
autocomplete.jumpBy(-5);
break;
case "Enter":
case "Tab":
e.preventDefault();
autocomplete.select();
break;
case "Escape":
e.preventDefault();
this.onBlur();
break;
case "Home":
autocomplete.jumpToTop();
break;
case "End":
autocomplete.jumpToBottom();
break;
}
},
render() {
let { type = "search", placeholder } = this.props;
let {
type = "search",
placeholder,
autocompleteList
} = this.props;
let { value } = this.state;
let divClassList = ["devtools-searchbox", "has-clear-btn"];
let inputClassList = [`devtools-${type}input`];
@ -96,14 +158,27 @@ module.exports = createClass({
dom.input({
className: inputClassList.join(" "),
onChange: this.onChange,
onFocus: this.onFocus,
onBlur: this.onBlur,
onKeyDown: this.onKeyDown,
placeholder,
ref: "input",
value
value,
}),
dom.button({
className: "devtools-searchinput-clear",
hidden: value == "",
onClick: this.onClearButtonClick
}),
autocompleteList.length > 0 && this.state.focused &&
AutocompletePopup({
list: autocompleteList,
filter: value,
ref: "autocomplete",
onItemSelected: (itemValue) => {
this.setState({ value: itemValue });
this.onChange();
}
})
);
}

Просмотреть файл

@ -82,35 +82,13 @@ html|button, html|select {
background-color: transparent;
border-radius: 4px;
padding: 1px 0;
}
.devtools-autocomplete-listbox .autocomplete-selected {
background-color: rgba(0,0,0,0.2);
}
.devtools-autocomplete-listbox.dark-theme .autocomplete-selected,
.devtools-autocomplete-listbox.dark-theme .autocomplete-item:hover {
background-color: rgba(0,0,0,0.5);
}
.devtools-autocomplete-listbox.dark-theme .autocomplete-selected > .autocomplete-value,
.devtools-autocomplete-listbox:focus.dark-theme .autocomplete-selected > .initial-value {
color: hsl(208,100%,60%);
}
.devtools-autocomplete-listbox.dark-theme .autocomplete-selected > span {
color: #eee;
}
.devtools-autocomplete-listbox.dark-theme .autocomplete-item > span {
color: #ccc;
cursor: default;
}
.devtools-autocomplete-listbox .autocomplete-item > .initial-value,
.devtools-autocomplete-listbox .autocomplete-item > .autocomplete-value {
margin: 0;
padding: 0;
cursor: default;
}
.devtools-autocomplete-listbox .autocomplete-item > .autocomplete-count {
@ -120,52 +98,60 @@ html|button, html|select {
/* Rest of the dark and light theme */
.devtools-autocomplete-popup,
.CodeMirror-hints,
.CodeMirror-Tern-tooltip {
border: 1px solid hsl(210,24%,90%);
background-image: linear-gradient(to bottom, hsla(209,18%,100%,0.9), hsl(210,24%,95%));
box-shadow: 0 1px 0 hsla(209,29%,90%,.25) inset;
}
.theme-dark .devtools-autocomplete-popup,
.theme-dark .CodeMirror-hints,
.theme-dark .CodeMirror-Tern-tooltip {
border: 1px solid hsl(210,11%,10%);
background-image: linear-gradient(to bottom, hsla(209,18%,18%,0.9), hsl(210,11%,16%));
}
.devtools-autocomplete-popup.light-theme,
.light-theme .CodeMirror-hints,
.light-theme .CodeMirror-Tern-tooltip {
border: 1px solid hsl(210,24%,90%);
background-image: linear-gradient(to bottom, hsla(209,18%,100%,0.9), hsl(210,24%,95%));
}
.devtools-autocomplete-popup.light-theme {
box-shadow: 0 1px 0 hsla(209,29%,90%,.25) inset;
box-shadow: none;
}
.theme-firebug .devtools-autocomplete-popup {
border-color: var(--theme-splitter-color);
border-radius: 5px;
font-size: var(--theme-autompletion-font-size);
}
.devtools-autocomplete-popup.firebug-theme {
background: var(--theme-body-background);
}
.devtools-autocomplete-listbox.firebug-theme .autocomplete-selected,
.devtools-autocomplete-listbox.firebug-theme .autocomplete-item:hover,
.devtools-autocomplete-listbox.light-theme .autocomplete-selected,
.devtools-autocomplete-listbox.light-theme .autocomplete-item:hover {
.devtools-autocomplete-listbox .autocomplete-selected,
.devtools-autocomplete-listbox .autocomplete-item:hover {
background-color: rgba(128,128,128,0.3);
}
.devtools-autocomplete-listbox.firebug-theme .autocomplete-selected > .autocomplete-value,
.devtools-autocomplete-listbox:focus.firebug-theme .autocomplete-selected > .initial-value,
.devtools-autocomplete-listbox.light-theme .autocomplete-selected > .autocomplete-value,
.devtools-autocomplete-listbox:focus.light-theme .autocomplete-selected > .initial-value {
.theme-dark .devtools-autocomplete-listbox .autocomplete-selected,
.theme-dark .devtools-autocomplete-listbox .autocomplete-item:hover {
background-color: rgba(0,0,0,0.5);
}
.devtools-autocomplete-listbox .autocomplete-selected > .autocomplete-value,
.devtools-autocomplete-listbox:focus .autocomplete-selected > .initial-value {
color: #222;
}
.devtools-autocomplete-listbox.firebug-theme .autocomplete-item > span,
.devtools-autocomplete-listbox.light-theme .autocomplete-item > span {
.theme-dark .devtools-autocomplete-listbox .autocomplete-selected > .autocomplete-value,
.theme-dark .devtools-autocomplete-listbox:focus .autocomplete-selected > .initial-value {
color: hsl(208,100%,60%);
}
.devtools-autocomplete-listbox .autocomplete-item > span {
color: #666;
}
.theme-dark .devtools-autocomplete-listbox .autocomplete-item > span {
color: #ccc;
}
.theme-dark .devtools-autocomplete-listbox .autocomplete-selected > span {
color: #eee;
}
/* Autocomplete list clone used for accessibility. */
.devtools-autocomplete-list-aria-clone {
@ -493,6 +479,14 @@ checkbox:-moz-focusring {
outline: none;
}
.devtools-searchbox .devtools-autocomplete-popup {
position: absolute;
top: 100%;
width: 100%;
line-height: initial !important;
z-index: 999;
}
/* Don't add 'double spacing' for inputs that are at beginning / end
of a toolbar (since the toolbar has it's own spacing). */
.devtools-toolbar > .devtools-textinput:first-child,