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