blob: 432913f91a1c80b3becdbbd0365a5cb7887f3801 [file] [log] [blame]
// Copyright (C) 2022 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import m from 'mithril';
import {Actions} from '../common/actions';
import {globals} from './globals';
export const LOG_PRIORITIES =
['-', '-', 'Verbose', 'Debug', 'Info', 'Warn', 'Error', 'Fatal'];
const IGNORED_STATES = 2;
interface LogPriorityWidgetAttrs {
options: string[];
selectedIndex: number;
onSelect: (id: number) => void;
}
interface LogTagChipAttrs {
name: string;
removeTag: (name: string) => void;
}
interface LogTagsWidgetAttrs {
tags: string[];
}
interface FilterByTextWidgetAttrs {
hideNonMatching: boolean;
}
class LogPriorityWidget implements m.ClassComponent<LogPriorityWidgetAttrs> {
view(vnode: m.Vnode<LogPriorityWidgetAttrs>) {
const attrs = vnode.attrs;
const optionComponents = [];
for (let i = IGNORED_STATES; i < attrs.options.length; i++) {
const selected = i === attrs.selectedIndex;
optionComponents.push(
m('option', {value: i, selected}, attrs.options[i]));
}
return m(
'select',
{
onchange: (e: InputEvent) => {
const selectionValue = (e.target as HTMLSelectElement).value;
attrs.onSelect(Number(selectionValue));
},
},
optionComponents,
);
}
}
class LogTagChip implements m.ClassComponent<LogTagChipAttrs> {
view({attrs}: m.CVnode<LogTagChipAttrs>) {
return m(
'.chip',
m('.chip-text', attrs.name),
m('button.chip-button',
{
onclick: () => {
attrs.removeTag(attrs.name);
},
},
'×'));
}
}
class LogTagsWidget implements m.ClassComponent<LogTagsWidgetAttrs> {
removeTag(tag: string) {
globals.dispatch(Actions.removeLogTag({tag}));
}
view(vnode: m.Vnode<LogTagsWidgetAttrs>) {
const tags = vnode.attrs.tags;
return m(
'.tag-container',
m('.chips', tags.map((tag) => m(LogTagChip, {
name: tag,
removeTag: this.removeTag.bind(this),
}))),
m(`input.chip-input[placeholder='Add new tag']`, {
onkeydown: (e: KeyboardEvent) => {
// This is to avoid zooming on 'w'(and other unexpected effects
// of key presses in this input field).
e.stopPropagation();
const htmlElement = e.target as HTMLInputElement;
// When the user clicks 'Backspace' we delete the previous tag.
if (e.key === 'Backspace' && tags.length > 0 &&
htmlElement.value === '') {
globals.dispatch(
Actions.removeLogTag({tag: tags[tags.length - 1]}));
return;
}
if (e.key !== 'Enter') {
return;
}
if (htmlElement.value === '') {
return;
}
globals.dispatch(
Actions.addLogTag({tag: htmlElement.value.trim()}));
htmlElement.value = '';
},
}));
}
}
class LogTextWidget implements m.ClassComponent {
view() {
return m(
'.tag-container', m(`input.chip-input[placeholder='Search log text']`, {
onkeydown: (e: KeyboardEvent) => {
// This is to avoid zooming on 'w'(and other unexpected effects
// of key presses in this input field).
e.stopPropagation();
},
onkeyup: (e: KeyboardEvent) => {
// We want to use the value of the input field after it has been
// updated with the latest key (onkeyup).
const htmlElement = e.target as HTMLInputElement;
globals.dispatch(
Actions.updateLogFilterText({textEntry: htmlElement.value}));
},
}));
}
}
class FilterByTextWidget implements m.ClassComponent<FilterByTextWidgetAttrs> {
view({attrs}: m.Vnode<FilterByTextWidgetAttrs>) {
const icon = attrs.hideNonMatching ? 'unfold_less' : 'unfold_more';
const tooltip = attrs.hideNonMatching ? 'Expand all and view highlighted' :
'Collapse all';
return m(
'.filter-widget',
m('.tooltip', tooltip),
m('i.material-icons',
{
onclick: () => {
globals.dispatch(Actions.toggleCollapseByTextEntry({}));
},
},
icon));
}
}
export class LogsFilters implements m.ClassComponent {
view(_: m.CVnode<{}>) {
return m(
'.log-filters',
m('.log-label', 'Log Level'),
m(LogPriorityWidget, {
options: LOG_PRIORITIES,
selectedIndex: globals.state.logFilteringCriteria.minimumLevel,
onSelect: (minimumLevel) => {
globals.dispatch(Actions.setMinimumLogLevel({minimumLevel}));
},
}),
m(LogTagsWidget, {tags: globals.state.logFilteringCriteria.tags}),
m(LogTextWidget),
m(FilterByTextWidget, {
hideNonMatching: globals.state.logFilteringCriteria.hideNonMatching,
}));
}
}