blob: ad515884c352351bca16ff98f7a511fcbd5c35fd [file] [log] [blame]
/*
* Copyright (C) 2013 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @unrestricted
*/
export default class FilterBar extends UI.HBox {
/**
* @param {string} name
* @param {boolean=} visibleByDefault
*/
constructor(name, visibleByDefault) {
super();
this.registerRequiredCSS('ui/filter.css');
this._enabled = true;
this.element.classList.add('filter-bar');
this._stateSetting = Common.settings.createSetting('filterBar-' + name + '-toggled', !!visibleByDefault);
this._filterButton = new UI.ToolbarSettingToggle(this._stateSetting, 'largeicon-filter', Common.UIString('Filter'));
this._filters = [];
this._updateFilterBar();
this._stateSetting.addChangeListener(this._updateFilterBar.bind(this));
}
/**
* @return {!UI.ToolbarButton}
*/
filterButton() {
return this._filterButton;
}
/**
* @param {!FilterUI} filter
*/
addFilter(filter) {
this._filters.push(filter);
this.element.appendChild(filter.element());
filter.addEventListener(FilterUI.Events.FilterChanged, this._filterChanged, this);
this._updateFilterButton();
}
setEnabled(enabled) {
this._enabled = enabled;
this._filterButton.setEnabled(enabled);
this._updateFilterBar();
}
forceShowFilterBar() {
this._alwaysShowFilters = true;
this._updateFilterBar();
}
showOnce() {
this._stateSetting.set(true);
}
/**
* @param {!Common.Event} event
*/
_filterChanged(event) {
this._updateFilterButton();
}
/**
* @override
*/
wasShown() {
super.wasShown();
this._updateFilterBar();
}
_updateFilterBar() {
if (!this.parentWidget() || this._showingWidget) {
return;
}
if (this.visible()) {
this._showingWidget = true;
this.showWidget();
this._showingWidget = false;
} else {
this.hideWidget();
}
}
/**
* @override
*/
focus() {
for (let i = 0; i < this._filters.length; ++i) {
if (this._filters[i] instanceof TextFilterUI) {
const textFilterUI = /** @type {!TextFilterUI} */ (this._filters[i]);
textFilterUI.focus();
break;
}
}
}
_updateFilterButton() {
let isActive = false;
for (const filter of this._filters) {
isActive = isActive || filter.isActive();
}
this._filterButton.setDefaultWithRedColor(isActive);
this._filterButton.setToggleWithRedColor(isActive);
}
clear() {
this.element.removeChildren();
this._filters = [];
this._updateFilterButton();
}
setting() {
return this._stateSetting;
}
visible() {
return this._alwaysShowFilters || (this._stateSetting.get() && this._enabled);
}
}
/**
* @interface
*/
export class FilterUI extends Common.EventTarget {
/**
* @return {boolean}
*/
isActive() {
}
/**
* @return {!Element}
*/
element() {}
}
/** @enum {symbol} */
FilterUI.Events = {
FilterChanged: Symbol('FilterChanged')
};
/**
* @implements {UI.FilterUI}
* @unrestricted
*/
export class TextFilterUI extends Common.Object {
constructor() {
super();
this._filterElement = createElement('div');
this._filterElement.className = 'filter-text-filter';
this._filterInputElement = this._filterElement.createChild('span', 'filter-input-field');
this._prompt = new UI.TextPrompt();
this._prompt.initialize(this._completions.bind(this), ' ');
this._proxyElement = this._prompt.attach(this._filterInputElement);
this._proxyElement.title = Common.UIString('e.g. /small[\\d]+/ url:a.com/b');
this._prompt.setPlaceholder(Common.UIString('Filter'));
this._prompt.addEventListener(UI.TextPrompt.Events.TextChanged, this._valueChanged.bind(this));
/** @type {?function(string, string, boolean=):!Promise<!UI.SuggestBox.Suggestions>} */
this._suggestionProvider = null;
}
/**
* @param {string} expression
* @param {string} prefix
* @param {boolean=} force
* @return {!Promise<!UI.SuggestBox.Suggestions>}
*/
_completions(expression, prefix, force) {
if (this._suggestionProvider) {
return this._suggestionProvider(expression, prefix, force);
}
return Promise.resolve([]);
}
/**
* @override
* @return {boolean}
*/
isActive() {
return !!this._prompt.text();
}
/**
* @override
* @return {!Element}
*/
element() {
return this._filterElement;
}
/**
* @return {string}
*/
value() {
return this._prompt.textWithCurrentSuggestion();
}
/**
* @param {string} value
*/
setValue(value) {
this._prompt.setText(value);
this._valueChanged();
}
focus() {
this._filterInputElement.focus();
}
/**
* @param {(function(string, string, boolean=):!Promise<!UI.SuggestBox.Suggestions>)} suggestionProvider
*/
setSuggestionProvider(suggestionProvider) {
this._prompt.clearAutocomplete();
this._suggestionProvider = suggestionProvider;
}
_valueChanged() {
this.dispatchEventToListeners(FilterUI.Events.FilterChanged, null);
}
}
/**
* @implements {FilterUI}
* @unrestricted
*/
export class NamedBitSetFilterUI extends Common.Object {
/**
* @param {!Array.<!UI.NamedBitSetFilterUI.Item>} items
* @param {!Common.Setting=} setting
*/
constructor(items, setting) {
super();
this._filtersElement = createElementWithClass('div', 'filter-bitset-filter');
UI.ARIAUtils.markAsListBox(this._filtersElement);
UI.ARIAUtils.markAsMultiSelectable(this._filtersElement);
this._filtersElement.title = Common.UIString(
'%sClick to select multiple types',
UI.KeyboardShortcut.shortcutToString('', UI.KeyboardShortcut.Modifiers.CtrlOrMeta));
this._allowedTypes = {};
/** @type {!Array.<!Element>} */
this._typeFilterElements = [];
this._addBit(NamedBitSetFilterUI.ALL_TYPES, Common.UIString('All'));
this._typeFilterElements[0].tabIndex = 0;
this._filtersElement.createChild('div', 'filter-bitset-filter-divider');
for (let i = 0; i < items.length; ++i) {
this._addBit(items[i].name, items[i].label, items[i].title);
}
if (setting) {
this._setting = setting;
setting.addChangeListener(this._settingChanged.bind(this));
this._settingChanged();
} else {
this._toggleTypeFilter(NamedBitSetFilterUI.ALL_TYPES, false /* allowMultiSelect */);
}
}
reset() {
this._toggleTypeFilter(NamedBitSetFilterUI.ALL_TYPES, false /* allowMultiSelect */);
}
/**
* @override
* @return {boolean}
*/
isActive() {
return !this._allowedTypes[NamedBitSetFilterUI.ALL_TYPES];
}
/**
* @override
* @return {!Element}
*/
element() {
return this._filtersElement;
}
/**
* @param {string} typeName
* @return {boolean}
*/
accept(typeName) {
return !!this._allowedTypes[NamedBitSetFilterUI.ALL_TYPES] || !!this._allowedTypes[typeName];
}
_settingChanged() {
const allowedTypes = this._setting.get();
this._allowedTypes = {};
for (const element of this._typeFilterElements) {
if (allowedTypes[element.typeName]) {
this._allowedTypes[element.typeName] = true;
}
}
this._update();
}
_update() {
if ((Object.keys(this._allowedTypes).length === 0) || this._allowedTypes[NamedBitSetFilterUI.ALL_TYPES]) {
this._allowedTypes = {};
this._allowedTypes[NamedBitSetFilterUI.ALL_TYPES] = true;
}
for (const element of this._typeFilterElements) {
const typeName = element.typeName;
const active = !!this._allowedTypes[typeName];
element.classList.toggle('selected', active);
UI.ARIAUtils.setSelected(element, active);
}
this.dispatchEventToListeners(FilterUI.Events.FilterChanged, null);
}
/**
* @param {string} name
* @param {string} label
* @param {string=} title
*/
_addBit(name, label, title) {
const typeFilterElement = this._filtersElement.createChild('span', name);
typeFilterElement.tabIndex = -1;
typeFilterElement.typeName = name;
typeFilterElement.createTextChild(label);
UI.ARIAUtils.markAsOption(typeFilterElement);
if (title) {
typeFilterElement.title = title;
}
typeFilterElement.addEventListener('click', this._onTypeFilterClicked.bind(this), false);
typeFilterElement.addEventListener('keydown', this._onTypeFilterKeydown.bind(this), false);
this._typeFilterElements.push(typeFilterElement);
}
/**
* @param {!Event} e
*/
_onTypeFilterClicked(e) {
let toggle;
if (Host.isMac()) {
toggle = e.metaKey && !e.ctrlKey && !e.altKey && !e.shiftKey;
} else {
toggle = e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey;
}
this._toggleTypeFilter(e.target.typeName, toggle);
}
/**
* @param {!Event} event
*/
_onTypeFilterKeydown(event) {
const element = /** @type {?Element} */ (event.target);
if (!element) {
return;
}
if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') {
if (this._keyFocusNextBit(element, true /* selectPrevious */)) {
event.consume(true);
}
} else if (event.key === 'ArrowRight' || event.key === 'ArrowDown') {
if (this._keyFocusNextBit(element, false /* selectPrevious */)) {
event.consume(true);
}
} else if (isEnterOrSpaceKey(event)) {
this._onTypeFilterClicked(event);
}
}
/**
* @param {!Element} target
* @param {boolean} selectPrevious
* @returns {!boolean}
*/
_keyFocusNextBit(target, selectPrevious) {
const index = this._typeFilterElements.indexOf(target);
if (index === -1) {
return false;
}
const nextIndex = selectPrevious ? index - 1 : index + 1;
if (nextIndex < 0 || nextIndex >= this._typeFilterElements.length) {
return false;
}
const nextElement = this._typeFilterElements[nextIndex];
nextElement.tabIndex = 0;
target.tabIndex = -1;
nextElement.focus();
return true;
}
/**
* @param {string} typeName
* @param {boolean} allowMultiSelect
*/
_toggleTypeFilter(typeName, allowMultiSelect) {
if (allowMultiSelect && typeName !== NamedBitSetFilterUI.ALL_TYPES) {
this._allowedTypes[NamedBitSetFilterUI.ALL_TYPES] = false;
} else {
this._allowedTypes = {};
}
this._allowedTypes[typeName] = !this._allowedTypes[typeName];
if (this._setting) {
this._setting.set(this._allowedTypes);
} else {
this._update();
}
}
}
NamedBitSetFilterUI.ALL_TYPES = 'all';
/**
* @implements {UI.FilterUI}
* @unrestricted
*/
export class CheckboxFilterUI extends Common.Object {
/**
* @param {string} className
* @param {string} title
* @param {boolean=} activeWhenChecked
* @param {!Common.Setting=} setting
*/
constructor(className, title, activeWhenChecked, setting) {
super();
this._filterElement = createElementWithClass('div', 'filter-checkbox-filter');
this._activeWhenChecked = !!activeWhenChecked;
this._label = UI.CheckboxLabel.create(title);
this._filterElement.appendChild(this._label);
this._checkboxElement = this._label.checkboxElement;
if (setting) {
UI.SettingsUI.bindCheckbox(this._checkboxElement, setting);
} else {
this._checkboxElement.checked = true;
}
this._checkboxElement.addEventListener('change', this._fireUpdated.bind(this), false);
}
/**
* @override
* @return {boolean}
*/
isActive() {
return this._activeWhenChecked === this._checkboxElement.checked;
}
/**
* @return {boolean}
*/
checked() {
return this._checkboxElement.checked;
}
/**
* @param {boolean} checked
*/
setChecked(checked) {
this._checkboxElement.checked = checked;
}
/**
* @override
* @return {!Element}
*/
element() {
return this._filterElement;
}
/**
* @return {!Element}
*/
labelElement() {
return this._label;
}
_fireUpdated() {
this.dispatchEventToListeners(FilterUI.Events.FilterChanged, null);
}
/**
* @param {string} backgroundColor
* @param {string} borderColor
*/
setColor(backgroundColor, borderColor) {
this._label.backgroundColor = backgroundColor;
this._label.borderColor = borderColor;
}
}
/* Legacy exported object*/
self.UI = self.UI || {};
/* Legacy exported object*/
UI = UI || {};
/** @constructor */
UI.FilterBar = FilterBar;
/** @interface */
UI.FilterUI = FilterUI;
/** @constructor */
UI.TextFilterUI = TextFilterUI;
/** @constructor */
UI.NamedBitSetFilterUI = NamedBitSetFilterUI;
/** @constructor */
UI.CheckboxFilterUI = CheckboxFilterUI;
/** @typedef {{name: string, label: string, title: (string|undefined)}} */
UI.NamedBitSetFilterUI.Item;