Bug 1287508 - Part 1: New console frontend: Add filter bar. r=bgrins

MozReview-Commit-ID: KoIEkQVPg2V

--HG--
extra : rebase_source : 4d07e951076d599f81973f85f2b42762a5de522a
This commit is contained in:
Nicolas Chevobbe 2016-07-18 19:02:19 +02:00
parent e5fea6eaa1
commit 2f72f50306
22 changed files with 484 additions and 10 deletions

View File

@ -248,6 +248,8 @@
.devtools-button[checked]:empty::before,
.devtools-button[open]:empty::before,
.devtools-button.active::before,
.theme-light .devtools-button.active::before,
.devtools-toolbarbutton:not([label])[checked=true] > image,
.devtools-toolbarbutton:not([label])[open=true] > image {
filter: var(--checked-icon-filter);
@ -308,6 +310,10 @@
background-image: var(--clear-icon-url);
}
.devtools-button.devtools-filter-icon::before {
background-image: var(--filter-image);
}
.devtools-toolbarbutton.devtools-clear-icon {
list-style-image: var(--clear-icon-url);
}
@ -649,3 +655,28 @@
transform: rotate(360deg);
}
}
/* Filter buttons */
.menu-filter-button {
-moz-appearance: none;
background: rgba(128,128,128,0.1);
border: none;
border-radius: 2px;
min-width: 0;
padding: 0 5px;
margin: 2px;
color: var(--theme-body-color);
}
.menu-filter-button:hover {
background: rgba(128,128,128,0.2);
}
.menu-filter-button:hover:active {
background-color: var(--theme-selection-background-semitransparent);
}
.menu-filter-button:not(:active).active {
background-color: var(--theme-selection-background);
color: var(--theme-selection-color);
}

View File

@ -151,6 +151,7 @@ a {
direction: ltr;
overflow: auto;
-moz-user-select: text;
position: relative;
}
/* The width on #output-container is set to a hardcoded px in webconsole.js
@ -624,3 +625,41 @@ a.learn-more-link.webconsole-learn-more-link {
border-left: 1px solid #D7D7D7;
border-right: 1px solid #D7D7D7;
}
/* NEW CONSOLE STYLES */
#output-wrapper > div {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
#output-container {
height: 100%;
}
.webconsole-output-wrapper {
display: flex;
flex-direction: column;
height: 100%;
}
.webconsole-filteringbar-wrapper {
flex-grow: 0;
}
.webconsole-output {
flex: 1;
overflow: auto;
}
.webconsole-filteringbar-primary {
display: flex;
}
.webconsole-filteringbar-primary .devtools-searchinput {
flex: 1 1 100%;
}

View File

@ -13,6 +13,9 @@ const {
const {
MESSAGE_ADD,
MESSAGES_CLEAR,
SEVERITY_FILTER,
MESSAGES_SEARCH,
FILTERS_CLEAR,
} = require("../constants");
function messageAdd(packet) {
@ -29,5 +32,29 @@ function messagesClear() {
};
}
function severityFilter(filter, toggled) {
return {
type: SEVERITY_FILTER,
filter,
toggled
};
}
function filtersClear() {
return {
type: FILTERS_CLEAR
};
}
function messagesSearch(searchText) {
return {
type: MESSAGES_SEARCH,
searchText
};
}
exports.messageAdd = messageAdd;
exports.messagesClear = messagesClear;
exports.severityFilter = severityFilter;
exports.filtersClear = filtersClear;
exports.messagesSearch = messagesSearch;

View File

@ -5,4 +5,5 @@
DevToolsModules(
'messages.js',
'ui.js',
)

View File

@ -0,0 +1,19 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* 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 {
FILTERBAR_TOGGLE,
} = require("../constants");
function filterBarToggle(show) {
return {
type: FILTERBAR_TOGGLE
};
}
exports.filterBarToggle = filterBarToggle;

View File

@ -48,7 +48,7 @@ const ConsoleOutput = createClass({
);
});
return (
dom.div({}, messageNodes)
dom.div({className: "webconsole-output"}, messageNodes)
);
}
});

View File

@ -0,0 +1,133 @@
/* 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 {
createFactory,
createClass,
DOM: dom,
PropTypes
} = require("devtools/client/shared/vendor/react");
const { connect } = require("devtools/client/shared/vendor/react-redux");
const { getAllFilters } = require("devtools/client/webconsole/new-console-output/selectors/filters");
const { getAllUi } = require("devtools/client/webconsole/new-console-output/selectors/ui");
const messagesActions = require("devtools/client/webconsole/new-console-output/actions/messages");
const uiActions = require("devtools/client/webconsole/new-console-output/actions/ui");
const { store } = require("devtools/client/webconsole/new-console-output/store");
const {
SEVERITY_FILTER
} = require("../constants");
const FilterToggleButton = createFactory(require("devtools/client/webconsole/new-console-output/components/filter-toggle-button").FilterToggleButton);
const FilterBar = createClass({
displayName: "FilterBar",
propTypes: {
filter: PropTypes.object.isRequired,
ui: PropTypes.object.isRequired
},
onClearOutputButtonClick: function () {
store.dispatch(messagesActions.messagesClear());
},
onToggleFilterConfigBarButtonClick: function () {
store.dispatch(uiActions.filterBarToggle());
},
onClearFiltersButtonClick: function () {
store.dispatch(messagesActions.filtersClear());
},
onSearchInput: function (e) {
store.dispatch(messagesActions.messagesSearch(e.target.value));
},
render() {
const {filter, ui} = this.props;
let configFilterBarVisible = ui.configFilterBarVisible;
let children = [];
children.push(dom.div({className: "devtools-toolbar webconsole-filteringbar-primary"},
dom.button({
className: "devtools-button devtools-clear-icon",
title: "Clear output",
onClick: this.onClearOutputButtonClick
}),
dom.button({
className: "devtools-button devtools-filter-icon" + (
configFilterBarVisible ? " active" : ""),
title: "Toggle filter bar",
onClick: this.onToggleFilterConfigBarButtonClick
}),
dom.input({
className: "devtools-searchinput",
type: "search",
value: filter.searchText,
placeholder: "Filter output",
onInput: this.onSearchInput
})
));
if (configFilterBarVisible) {
children.push(
dom.div({className: "devtools-toolbar"},
FilterToggleButton({
active: filter.error,
label: "Errors",
filterType: SEVERITY_FILTER,
filterKey: "error"}),
FilterToggleButton({
active: filter.warn,
label: "Warnings",
filterType: SEVERITY_FILTER,
filterKey: "warn"}),
FilterToggleButton({
active: filter.log,
label: "Logs",
filterType: SEVERITY_FILTER,
filterKey: "log"}),
FilterToggleButton({
active: filter.info,
label: "Info",
filterType: SEVERITY_FILTER,
filterKey: "info"})
)
);
}
if (ui.filteredMessageVisible) {
children.push(
dom.div({className: "devtools-toolbar"},
dom.span({
className: "clear"},
"You have filters set that may hide some results. " +
"Learn more about our filtering syntax ",
dom.a({}, "here"),
"."),
dom.button({
className: "menu-filter-button",
onClick: this.onClearFiltersButtonClick
}, "Remove filters")
)
);
}
return (
dom.div({className: "webconsole-filteringbar-wrapper"},
children
)
);
}
});
function mapStateToProps(state) {
return {
filter: getAllFilters(state),
ui: getAllUi(state)
};
}
module.exports = connect(mapStateToProps)(FilterBar);

View File

@ -0,0 +1,50 @@
/* 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 {
createClass,
DOM: dom,
PropTypes
} = require("devtools/client/shared/vendor/react");
const { store } = require("devtools/client/webconsole/new-console-output/store");
const actions = require("devtools/client/webconsole/new-console-output/actions/messages");
const {
SEVERITY_FILTER
} = require("../constants");
const FilterToggleButton = createClass({
displayName: "FilterToggleButton",
propTypes: {
label: PropTypes.string.isRequired,
filterType: PropTypes.string.isRequired,
filterKey: PropTypes.string.isRequired,
active: PropTypes.bool.isRequired,
},
onClick: function () {
if (this.props.filterType === SEVERITY_FILTER) {
store.dispatch(actions.severityFilter(
this.props.filterKey, !this.props.active));
}
},
render() {
const {label, active} = this.props;
let classList = ["menu-filter-button"];
if (active) {
classList.push("active");
}
return dom.button({
className: classList.join(" "),
onClick: this.onClick
}, label);
}
});
exports.FilterToggleButton = FilterToggleButton;

View File

@ -9,6 +9,8 @@ DIRS += [
DevToolsModules(
'console-output.js',
'filter-bar.js',
'filter-toggle-button.js',
'grip-message-body.js',
'message-container.js',
'message-icon.js',

View File

@ -8,6 +8,10 @@
const actionTypes = {
MESSAGE_ADD: "MESSAGE_ADD",
MESSAGES_CLEAR: "MESSAGES_CLEAR",
SEVERITY_FILTER: "SEVERITY_FILTER",
MESSAGES_SEARCH: "MESSAGES_SEARCH",
FILTERS_CLEAR: "FILTERS_CLEAR",
FILTERBAR_TOGGLE: "FILTERBAR_TOGGLE",
};
const categories = {
@ -92,6 +96,10 @@ const chromeRDPEnums = {
}
};
const filterTypes = {
SEVERITY_FILTER: "SEVERITY_FILTER"
};
// Combine into a single constants object
module.exports = Object.assign({}, actionTypes, categories, severities, levels,
chromeRDPEnums);
chromeRDPEnums, filterTypes);

View File

@ -12,11 +12,19 @@ const actions = require("devtools/client/webconsole/new-console-output/actions/m
const { store } = require("devtools/client/webconsole/new-console-output/store");
const ConsoleOutput = React.createFactory(require("devtools/client/webconsole/new-console-output/components/console-output"));
const FilterBar = React.createFactory(require("devtools/client/webconsole/new-console-output/components/filter-bar"));
function NewConsoleOutputWrapper(parentNode, jsterm) {
let childComponent = ConsoleOutput({ jsterm });
let filterBar = FilterBar({});
let provider = React.createElement(
Provider, { store: store }, childComponent);
Provider,
{ store: store },
React.DOM.div(
{className: "webconsole-output-wrapper"},
filterBar,
childComponent
));
this.body = ReactDOM.render(provider, parentNode);
}

View File

@ -0,0 +1,26 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* 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 constants = require("devtools/client/webconsole/new-console-output/constants");
const {Filters} = require("devtools/client/webconsole/new-console-output/store");
function filters(state = new Filters(), action) {
switch (action.type) {
case constants.SEVERITY_FILTER:
let {filter, toggled} = action;
return state.set(filter, toggled);
case constants.FILTERS_CLEAR:
return new Filters();
case constants.MESSAGES_SEARCH:
let {searchText} = action;
return state.set("searchText", searchText);
}
return state;
}
exports.filters = filters;

View File

@ -5,10 +5,14 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { filters } = require("./filters");
const { messages } = require("./messages");
const { prefs } = require("./prefs");
const { ui } = require("./ui");
exports.reducers = {
filters,
messages,
prefs,
ui,
};

View File

@ -4,7 +4,9 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DevToolsModules(
'filters.js',
'index.js',
'messages.js',
'prefs.js',
'ui.js',
)

View File

@ -5,7 +5,9 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
function prefs(state = {}, action) {
const {Prefs} = require("devtools/client/webconsole/new-console-output/store");
function prefs(state = new Prefs(), action) {
return state;
}

View File

@ -0,0 +1,20 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* 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 constants = require("devtools/client/webconsole/new-console-output/constants");
const {Ui} = require("devtools/client/webconsole/new-console-output/store");
function ui(state = new Ui(), action) {
switch (action.type) {
case constants.FILTERBAR_TOGGLE:
return state.set("configFilterBarVisible", !state.configFilterBarVisible);
}
return state;
}
exports.ui = ui;

View File

@ -0,0 +1,12 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* 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";
function getAllFilters(state) {
return state.filters;
}
exports.getAllFilters = getAllFilters;

View File

@ -5,13 +5,46 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { getAllFilters } = require("devtools/client/webconsole/new-console-output/selectors/filters");
const { getLogLimit } = require("devtools/client/webconsole/new-console-output/selectors/prefs");
function getAllMessages(state) {
let messages = state.messages;
let messageCount = messages.count();
let logLimit = getLogLimit(state);
let filters = getAllFilters(state);
return prune(
search(
filterSeverity(messages, filters),
filters.searchText
),
logLimit
);
}
function filterSeverity(messages, filters) {
return messages.filter((message) => filters[message.severity] === true);
}
function search(messages, searchText = "") {
if (searchText === "") {
return messages;
}
return messages.filter(function (message) {
// @TODO: message.parameters can be a grip, see how we can handle that
if (!Array.isArray(message.parameters)) {
return true;
}
return message
.parameters.join("")
.toLocaleLowerCase()
.includes(searchText.toLocaleLowerCase());
});
}
function prune(messages, logLimit) {
let messageCount = messages.count();
if (messageCount > logLimit) {
return messages.splice(0, messageCount - logLimit);
}

View File

@ -4,6 +4,8 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DevToolsModules(
'filters.js',
'messages.js',
'prefs.js',
'ui.js',
)

View File

@ -0,0 +1,12 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* 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";
function getAllUi(state) {
return state.ui;
}
exports.getAllUi = getAllUi;

View File

@ -3,17 +3,50 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { combineReducers, createStore } = require("devtools/client/shared/vendor/redux");
const Immutable = require("devtools/client/shared/vendor/immutable");
const { reducers } = require("./reducers/index");
const Services = require("Services");
const Filters = Immutable.Record({
error: true,
warn: true,
info: true,
log: true,
searchText: ""
});
const Prefs = Immutable.Record({
logLimit: 1000
});
const Ui = Immutable.Record({
configFilterBarVisible: false,
filteredMessageVisible: false
});
module.exports.Prefs = Prefs;
module.exports.Filters = Filters;
module.exports.Ui = Ui;
const { combineReducers, createStore } = require("devtools/client/shared/vendor/redux");
const { reducers } = require("./reducers/index");
function storeFactory() {
const initialState = {
messages: Immutable.List(),
prefs: {
logLimit: Math.max(Services.prefs.getIntPref("devtools.hud.loglimit"), 1)
}
prefs: new Prefs({
logLimit: Math.max(Services.prefs.getIntPref("devtools.hud.loglimit"), 1),
}),
filters: new Filters({
error: Services.prefs.getBoolPref("devtools.webconsole.filter.error"),
warn: Services.prefs.getBoolPref("devtools.webconsole.filter.warn"),
info: Services.prefs.getBoolPref("devtools.webconsole.filter.info"),
log: Services.prefs.getBoolPref("devtools.webconsole.filter.log"),
searchText: ""
}),
ui: new Ui({
configFilterBarVisible: false,
filteredMessageVisible: false
})
};
return createStore(combineReducers(reducers), initialState);

View File

@ -553,6 +553,9 @@ WebConsoleFrame.prototype = {
// in JSTerm is still necessary.
this.newConsoleOutput = new this.window.NewConsoleOutput(this.experimentalOutputNode, this.jsterm);
console.log("Created newConsoleOutput", this.newConsoleOutput);
let filterToolbar = doc.querySelector(".hud-console-filter-toolbar");
filterToolbar.hidden = true;
}
this.resize();
@ -586,6 +589,13 @@ WebConsoleFrame.prototype = {
return;
}
// Do not focus if a search input was clicked on the new frontend
if (this.NEW_CONSOLE_OUTPUT_ENABLED &&
event.target.nodeName.toLowerCase() === "input" &&
event.target.getAttribute("type").toLowerCase() === "search") {
return;
}
this.jsterm.focus();
});