20 changed files with 88 additions and 259 deletions
@ -1,41 +0,0 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2025 The Thingsboard Authors |
|||
|
|||
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. |
|||
|
|||
--> |
|||
<mat-form-field [appearance]="appearance" [subscriptSizing]="subscriptSizing" style="width: 100%"> |
|||
<mat-label *ngIf="label">{{label}}</mat-label> |
|||
<input matInput #nameInput [formControl]="selectionFormControl" |
|||
[placeholder]="placeholderText" |
|||
(focusin)="onFocus()" |
|||
[matAutocomplete]="optionsAutocomplete"> |
|||
<button *ngIf="selectionFormControl.value && !disabled" |
|||
type="button" |
|||
class="tb-icon-24 mr-2" |
|||
matSuffix mat-icon-button aria-label="Clear" |
|||
(click)="clear()"> |
|||
<mat-icon class="material-icons">close</mat-icon> |
|||
</button> |
|||
<mat-error *ngIf="selectionFormControl.hasError('required')"> |
|||
{{errorText}} |
|||
</mat-error> |
|||
<mat-autocomplete |
|||
#optionsAutocomplete="matAutocomplete" |
|||
class="tb-autocomplete tb-options-input-autocomplete"> |
|||
<mat-option *ngFor="let option of filteredOptions$ | async" [value]="option"> |
|||
<span class="tb-option-name flex-1" [innerHTML]="option | highlight:searchText:true:'ig'"></span> |
|||
</mat-option> |
|||
</mat-autocomplete> |
|||
</mat-form-field> |
|||
@ -1,161 +0,0 @@ |
|||
///
|
|||
/// Copyright © 2016-2025 The Thingsboard Authors
|
|||
///
|
|||
/// 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 { Component, ElementRef, forwardRef, Input, OnChanges, OnInit, SimpleChanges, ViewChild } from '@angular/core'; |
|||
import { ControlValueAccessor, FormBuilder, FormControl, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; |
|||
import { Observable } from 'rxjs'; |
|||
import { map, startWith, tap } from 'rxjs/operators'; |
|||
import { TranslateService } from '@ngx-translate/core'; |
|||
import { coerceBoolean } from '@shared/decorators/coercion'; |
|||
import { MatFormFieldAppearance, SubscriptSizing } from '@angular/material/form-field'; |
|||
import { AiModelMap, AiProvider } from '@shared/models/ai-model.models'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-models-list-autocomplete', |
|||
templateUrl: './models-list-autocomplete.component.html', |
|||
styleUrls: [], |
|||
providers: [ |
|||
{ |
|||
provide: NG_VALUE_ACCESSOR, |
|||
useExisting: forwardRef(() => ModelsListAutocompleteComponent), |
|||
multi: true |
|||
} |
|||
] |
|||
}) |
|||
export class ModelsListAutocompleteComponent implements ControlValueAccessor, OnInit, OnChanges { |
|||
|
|||
@ViewChild('nameInput', {static: true}) nameInput: ElementRef; |
|||
|
|||
@Input() |
|||
disabled: boolean; |
|||
|
|||
@Input() |
|||
@coerceBoolean() |
|||
required = false; |
|||
|
|||
@Input() |
|||
provider: AiProvider; |
|||
|
|||
@Input() |
|||
placeholderText: string = this.translate.instant('widget-config.set'); |
|||
|
|||
@Input() |
|||
subscriptSizing: SubscriptSizing = 'dynamic'; |
|||
|
|||
@Input() |
|||
appearance: MatFormFieldAppearance = 'outline'; |
|||
|
|||
@Input() |
|||
label: string; |
|||
|
|||
@Input() |
|||
errorText: string; |
|||
|
|||
selectionFormControl: FormControl; |
|||
modelValue: string | null; |
|||
|
|||
filteredOptions$: Observable<Array<string>>; |
|||
|
|||
searchText = ''; |
|||
|
|||
private dirty = false; |
|||
|
|||
private propagateChange = (_val: any) => {}; |
|||
|
|||
constructor(private fb: FormBuilder, |
|||
private translate: TranslateService) { |
|||
} |
|||
|
|||
ngOnInit() { |
|||
this.selectionFormControl = this.fb.control('', this.required ? [Validators.required] : []); |
|||
this.setupFilteredOptions(); |
|||
} |
|||
|
|||
ngOnChanges(changes: SimpleChanges) { |
|||
if (changes.provider && !changes.provider.isFirstChange()) { |
|||
this.setupFilteredOptions(); |
|||
this.selectionFormControl.setValue(null, {emitEvent: false}); |
|||
this.modelValue = null; |
|||
this.propagateChange(null); |
|||
} |
|||
} |
|||
|
|||
private setupFilteredOptions() { |
|||
this.filteredOptions$ = this.selectionFormControl.valueChanges.pipe( |
|||
startWith(''), |
|||
tap(value => this.updateView(value)), |
|||
map(value => { |
|||
const search = value ? value.toLowerCase() : ''; |
|||
const options = this.provider ? AiModelMap.get(this.provider).modelList || [] : []; |
|||
return search ? options.filter(option => option.toLowerCase().includes(search)) : options; |
|||
}) |
|||
); |
|||
} |
|||
|
|||
writeValue(option?: string): void { |
|||
this.searchText = ''; |
|||
this.modelValue = option ? option : null; |
|||
|
|||
if (option) { |
|||
this.selectionFormControl.patchValue(option, { emitEvent: false }); |
|||
this.dirty = true; |
|||
} else { |
|||
this.selectionFormControl.patchValue(null, { emitEvent: false }); |
|||
this.dirty = true; |
|||
} |
|||
} |
|||
|
|||
onFocus() { |
|||
if (this.dirty) { |
|||
this.selectionFormControl.updateValueAndValidity({onlySelf: true, emitEvent: true}); |
|||
this.dirty = false; |
|||
} |
|||
} |
|||
|
|||
updateView(value: string) { |
|||
this.searchText = value ? value : ''; |
|||
if (this.modelValue !== value && value) { |
|||
this.modelValue = value; |
|||
this.propagateChange(this.modelValue); |
|||
} |
|||
} |
|||
|
|||
registerOnChange(fn: any): void { |
|||
this.propagateChange = fn; |
|||
} |
|||
|
|||
registerOnTouched(fn: any): void { |
|||
} |
|||
|
|||
setDisabledState(isDisabled: boolean): void { |
|||
this.disabled = isDisabled; |
|||
if (this.disabled) { |
|||
this.selectionFormControl.disable({emitEvent: false}); |
|||
} else { |
|||
this.selectionFormControl.enable({emitEvent: false}); |
|||
} |
|||
} |
|||
|
|||
clear() { |
|||
this.selectionFormControl.patchValue(null, {emitEvent: true}); |
|||
this.propagateChange(null); |
|||
this.modelValue = null; |
|||
setTimeout(() => { |
|||
this.nameInput.nativeElement.blur(); |
|||
this.nameInput.nativeElement.focus(); |
|||
}, 0); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue