• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (C) 2022 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15import m from 'mithril';
16
17import {Actions} from '../common/actions';
18import {globals} from './globals';
19
20export const LOG_PRIORITIES =
21    ['-', '-', 'Verbose', 'Debug', 'Info', 'Warn', 'Error', 'Fatal'];
22const IGNORED_STATES = 2;
23
24interface LogPriorityWidgetAttrs {
25  options: string[];
26  selectedIndex: number;
27  onSelect: (id: number) => void;
28}
29
30interface LogTagChipAttrs {
31  name: string;
32  removeTag: (name: string) => void;
33}
34
35interface LogTagsWidgetAttrs {
36  tags: string[];
37}
38
39interface FilterByTextWidgetAttrs {
40  hideNonMatching: boolean;
41}
42
43class LogPriorityWidget implements m.ClassComponent<LogPriorityWidgetAttrs> {
44  view(vnode: m.Vnode<LogPriorityWidgetAttrs>) {
45    const attrs = vnode.attrs;
46    const optionComponents = [];
47    for (let i = IGNORED_STATES; i < attrs.options.length; i++) {
48      const selected = i === attrs.selectedIndex;
49      optionComponents.push(
50          m('option', {value: i, selected}, attrs.options[i]));
51    }
52    return m(
53        'select',
54        {
55          onchange: (e: InputEvent) => {
56            const selectionValue = (e.target as HTMLSelectElement).value;
57            attrs.onSelect(Number(selectionValue));
58          },
59        },
60        optionComponents,
61    );
62  }
63}
64
65class LogTagChip implements m.ClassComponent<LogTagChipAttrs> {
66  view({attrs}: m.CVnode<LogTagChipAttrs>) {
67    return m(
68        '.chip',
69        m('.chip-text', attrs.name),
70        m('button.chip-button',
71          {
72            onclick: () => {
73              attrs.removeTag(attrs.name);
74            },
75          },
76          '×'));
77  }
78}
79
80class LogTagsWidget implements m.ClassComponent<LogTagsWidgetAttrs> {
81  removeTag(tag: string) {
82    globals.dispatch(Actions.removeLogTag({tag}));
83  }
84
85  view(vnode: m.Vnode<LogTagsWidgetAttrs>) {
86    const tags = vnode.attrs.tags;
87    return m(
88        '.tag-container',
89        m('.chips', tags.map((tag) => m(LogTagChip, {
90                               name: tag,
91                               removeTag: this.removeTag.bind(this),
92                             }))),
93        m(`input.chip-input[placeholder='Add new tag']`, {
94          onkeydown: (e: KeyboardEvent) => {
95            // This is to avoid zooming on 'w'(and other unexpected effects
96            // of key presses in this input field).
97            e.stopPropagation();
98            const htmlElement = e.target as HTMLInputElement;
99
100            // When the user clicks 'Backspace' we delete the previous tag.
101            if (e.key === 'Backspace' && tags.length > 0 &&
102                htmlElement.value === '') {
103              globals.dispatch(
104                  Actions.removeLogTag({tag: tags[tags.length - 1]}));
105              return;
106            }
107
108            if (e.key !== 'Enter') {
109              return;
110            }
111            if (htmlElement.value === '') {
112              return;
113            }
114            globals.dispatch(
115                Actions.addLogTag({tag: htmlElement.value.trim()}));
116            htmlElement.value = '';
117          },
118        }));
119  }
120}
121
122class LogTextWidget implements m.ClassComponent {
123  view() {
124    return m(
125        '.tag-container', m(`input.chip-input[placeholder='Search log text']`, {
126          onkeydown: (e: KeyboardEvent) => {
127            // This is to avoid zooming on 'w'(and other unexpected effects
128            // of key presses in this input field).
129            e.stopPropagation();
130          },
131
132          onkeyup: (e: KeyboardEvent) => {
133            // We want to use the value of the input field after it has been
134            // updated with the latest key (onkeyup).
135            const htmlElement = e.target as HTMLInputElement;
136            globals.dispatch(
137                Actions.updateLogFilterText({textEntry: htmlElement.value}));
138          },
139        }));
140  }
141}
142
143class FilterByTextWidget implements m.ClassComponent<FilterByTextWidgetAttrs> {
144  view({attrs}: m.Vnode<FilterByTextWidgetAttrs>) {
145    const icon = attrs.hideNonMatching ? 'unfold_less' : 'unfold_more';
146    const tooltip = attrs.hideNonMatching ? 'Expand all and view highlighted' :
147                                            'Collapse all';
148    return m(
149        '.filter-widget',
150        m('.tooltip', tooltip),
151        m('i.material-icons',
152          {
153            onclick: () => {
154              globals.dispatch(Actions.toggleCollapseByTextEntry({}));
155            },
156          },
157          icon));
158  }
159}
160
161export class LogsFilters implements m.ClassComponent {
162  view(_: m.CVnode<{}>) {
163    return m(
164        '.log-filters',
165        m('.log-label', 'Log Level'),
166        m(LogPriorityWidget, {
167          options: LOG_PRIORITIES,
168          selectedIndex: globals.state.logFilteringCriteria.minimumLevel,
169          onSelect: (minimumLevel) => {
170            globals.dispatch(Actions.setMinimumLogLevel({minimumLevel}));
171          },
172        }),
173        m(LogTagsWidget, {tags: globals.state.logFilteringCriteria.tags}),
174        m(LogTextWidget),
175        m(FilterByTextWidget, {
176          hideNonMatching: globals.state.logFilteringCriteria.hideNonMatching,
177        }));
178  }
179}
180