xref: /aosp_15_r20/development/tools/winscope/src/viewers/components/search_box_component.ts (revision 90c8c64db3049935a07c6143d7fd006e26f8ecca)
1/*
2 * Copyright (C) 2024 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17import {Component, EventEmitter, Input, Output} from '@angular/core';
18import {assertDefined} from 'common/assert_utils';
19import {FilterFlag} from 'common/filter_flag';
20import {TextFilter} from 'viewers/common/text_filter';
21
22@Component({
23  selector: 'search-box',
24  template: `
25    <mat-form-field
26      *ngIf="textFilter"
27      [class]="getFormFieldClasses()"
28      [appearance]="appearance"
29      [style.height]="height"
30      (keydown.esc)="$event.target.blur()"
31      (keydown.enter)="$event.target.blur()">
32      <mat-label>{{ label }}</mat-label>
33      <input
34        matInput
35        [(ngModel)]="textFilter.filterString"
36        (ngModelChange)="onFilterChange()"
37        [name]="filterName" />
38      <div class="field-suffix" matSuffix>
39        <button
40          mat-icon-button
41          matTooltip="Match case"
42          [color]="hasFlag(FilterFlag.MATCH_CASE) ? 'primary' : undefined"
43          (click)="onFilterFlagClick($event, FilterFlag.MATCH_CASE)">
44          <mat-icon class="material-symbols-outlined">match_case</mat-icon>
45        </button>
46        <button
47          mat-icon-button
48          matTooltip="Match whole word"
49          [color]="hasFlag(FilterFlag.MATCH_WORD) ? 'primary' : undefined"
50          (click)="onFilterFlagClick($event, FilterFlag.MATCH_WORD)">
51          <mat-icon class="material-symbols-outlined">match_word</mat-icon>
52        </button>
53        <button
54          mat-icon-button
55          matTooltip="Use regex"
56          [color]="hasFlag(FilterFlag.USE_REGEX) ? 'primary' : undefined"
57          (click)="onFilterFlagClick($event, FilterFlag.USE_REGEX)">
58          <mat-icon class="material-symbols-outlined">regular_expression</mat-icon>
59        </button>
60      </div>
61    </mat-form-field>
62  `,
63  styles: [
64    `
65    .search-box {
66      font-size: 14px;
67      margin-top: 4px;
68    }
69    .search-box .mat-icon {
70      font-size: 18px;
71    }
72    .wide-field {
73      width: 100%;
74    }
75  `,
76  ],
77})
78export class SearchBoxComponent {
79  FilterFlag = FilterFlag;
80
81  @Input() textFilter: TextFilter | undefined = new TextFilter();
82  @Input() label = 'Search';
83  @Input() filterName = 'filter';
84  @Input() appearance = '';
85  @Input() formFieldClass = '';
86  @Input() height = '48px';
87
88  @Output() readonly filterChange = new EventEmitter<TextFilter>();
89
90  hasFlag(flag: FilterFlag): boolean {
91    return assertDefined(this.textFilter).flags.includes(flag) ?? false;
92  }
93
94  onFilterFlagClick(event: MouseEvent, flag: FilterFlag) {
95    event.stopPropagation();
96    const filter = assertDefined(this.textFilter);
97    if (this.hasFlag(flag)) {
98      filter.flags = filter.flags.filter((f) => f !== flag);
99    } else {
100      filter.flags = filter.flags.concat(flag);
101    }
102    this.onFilterChange();
103  }
104
105  onFilterChange() {
106    this.filterChange.emit(this.textFilter);
107  }
108
109  getFormFieldClasses(): string {
110    return (
111      'search-box ' +
112      ((this.textFilter?.filterString.length ?? 0) > 0 ? 'highlighted ' : '') +
113      this.formFieldClass
114    );
115  }
116}
117