54 changed files with 978 additions and 551 deletions
@ -1,291 +0,0 @@ |
|||||
///
|
|
||||
/// Copyright © 2016-2024 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.
|
|
||||
///
|
|
||||
|
|
||||
export const COUNTRIES = [ |
|
||||
'Afghanistan', |
|
||||
'Åland Islands', |
|
||||
'Albania', |
|
||||
'Algeria', |
|
||||
'American Samoa', |
|
||||
'Andorra', |
|
||||
'Angola', |
|
||||
'Anguilla', |
|
||||
'Antarctica', |
|
||||
'Antigua and Barbuda', |
|
||||
'Argentina', |
|
||||
'Armenia', |
|
||||
'Aruba', |
|
||||
'Australia', |
|
||||
'Austria', |
|
||||
'Azerbaijan', |
|
||||
'Bahamas', |
|
||||
'Bahrain', |
|
||||
'Bangladesh', |
|
||||
'Barbados', |
|
||||
'Belarus', |
|
||||
'Belgium', |
|
||||
'Belize', |
|
||||
'Benin', |
|
||||
'Bermuda', |
|
||||
'Bhutan', |
|
||||
'Bolivia', |
|
||||
'Bonaire, Sint Eustatius and Saba', |
|
||||
'Bosnia and Herzegovina', |
|
||||
'Botswana', |
|
||||
'Bouvet Island', |
|
||||
'Brazil', |
|
||||
'British Indian Ocean Territory', |
|
||||
'Brunei Darussalam', |
|
||||
'Bulgaria', |
|
||||
'Burkina Faso', |
|
||||
'Burundi', |
|
||||
'Cambodia', |
|
||||
'Cameroon', |
|
||||
'Canada', |
|
||||
'Cape Verde', |
|
||||
'Cayman Islands', |
|
||||
'Central African Republic', |
|
||||
'Chad', |
|
||||
'Chile', |
|
||||
'China', |
|
||||
'Christmas Island', |
|
||||
'Cocos (Keeling) Islands', |
|
||||
'Colombia', |
|
||||
'Comoros', |
|
||||
'Congo', |
|
||||
'Congo, The Democratic Republic of the', |
|
||||
'Cook Islands', |
|
||||
'Costa Rica', |
|
||||
'Côte d\'Ivoire', |
|
||||
'Croatia', |
|
||||
'Cuba', |
|
||||
'Curaçao', |
|
||||
'Cyprus', |
|
||||
'Czech Republic', |
|
||||
'Denmark', |
|
||||
'Djibouti', |
|
||||
'Dominica', |
|
||||
'Dominican Republic', |
|
||||
'Ecuador', |
|
||||
'Egypt', |
|
||||
'El Salvador', |
|
||||
'Equatorial Guinea', |
|
||||
'Eritrea', |
|
||||
'Estonia', |
|
||||
'Ethiopia', |
|
||||
'Falkland Islands (Malvinas)', |
|
||||
'Faroe Islands', |
|
||||
'Fiji', |
|
||||
'Finland', |
|
||||
'France', |
|
||||
'French Guiana', |
|
||||
'French Polynesia', |
|
||||
'French Southern Territories', |
|
||||
'Gabon', |
|
||||
'Gambia', |
|
||||
'Georgia', |
|
||||
'Germany', |
|
||||
'Ghana', |
|
||||
'Gibraltar', |
|
||||
'Greece', |
|
||||
'Greenland', |
|
||||
'Grenada', |
|
||||
'Guadeloupe', |
|
||||
'Guam', |
|
||||
'Guatemala', |
|
||||
'Guernsey', |
|
||||
'Guinea', |
|
||||
'Guinea-Bissau', |
|
||||
'Guyana', |
|
||||
'Haiti', |
|
||||
'Heard Island and McDonald Islands', |
|
||||
'Holy See (Vatican City State)', |
|
||||
'Honduras', |
|
||||
'Hong Kong', |
|
||||
'Hungary', |
|
||||
'Iceland', |
|
||||
'India', |
|
||||
'Indonesia', |
|
||||
'Iran, Islamic Republic of', |
|
||||
'Iraq', |
|
||||
'Ireland', |
|
||||
'Isle of Man', |
|
||||
'Israel', |
|
||||
'Italy', |
|
||||
'Jamaica', |
|
||||
'Japan', |
|
||||
'Jersey', |
|
||||
'Jordan', |
|
||||
'Kazakhstan', |
|
||||
'Kenya', |
|
||||
'Kiribati', |
|
||||
'Korea, Democratic People\'s Republic of', |
|
||||
'Korea, Republic of', |
|
||||
'Kuwait', |
|
||||
'Kyrgyzstan', |
|
||||
'Lao People\'s Democratic Republic', |
|
||||
'Latvia', |
|
||||
'Lebanon', |
|
||||
'Lesotho', |
|
||||
'Liberia', |
|
||||
'Libya', |
|
||||
'Liechtenstein', |
|
||||
'Lithuania', |
|
||||
'Luxembourg', |
|
||||
'Macao', |
|
||||
'Macedonia, Republic Of', |
|
||||
'Madagascar', |
|
||||
'Malawi', |
|
||||
'Malaysia', |
|
||||
'Maldives', |
|
||||
'Mali', |
|
||||
'Malta', |
|
||||
'Marshall Islands', |
|
||||
'Martinique', |
|
||||
'Mauritania', |
|
||||
'Mauritius', |
|
||||
'Mayotte', |
|
||||
'Mexico', |
|
||||
'Micronesia, Federated States of', |
|
||||
'Moldova, Republic of', |
|
||||
'Monaco', |
|
||||
'Mongolia', |
|
||||
'Montenegro', |
|
||||
'Montserrat', |
|
||||
'Morocco', |
|
||||
'Mozambique', |
|
||||
'Myanmar', |
|
||||
'Namibia', |
|
||||
'Nauru', |
|
||||
'Nepal', |
|
||||
'Netherlands', |
|
||||
'New Caledonia', |
|
||||
'New Zealand', |
|
||||
'Nicaragua', |
|
||||
'Niger', |
|
||||
'Nigeria', |
|
||||
'Niue', |
|
||||
'Norfolk Island', |
|
||||
'Northern Mariana Islands', |
|
||||
'Norway', |
|
||||
'Oman', |
|
||||
'Pakistan', |
|
||||
'Palau', |
|
||||
'Palestinian Territory, Occupied', |
|
||||
'Panama', |
|
||||
'Papua New Guinea', |
|
||||
'Paraguay', |
|
||||
'Peru', |
|
||||
'Philippines', |
|
||||
'Pitcairn', |
|
||||
'Poland', |
|
||||
'Portugal', |
|
||||
'Puerto Rico', |
|
||||
'Qatar', |
|
||||
'Reunion', |
|
||||
'Romania', |
|
||||
'Russian Federation', |
|
||||
'Rwanda', |
|
||||
'Saint Barthélemy', |
|
||||
'Saint Helena, Ascension and Tristan da Cunha', |
|
||||
'Saint Kitts and Nevis', |
|
||||
'Saint Lucia', |
|
||||
'Saint Martin (French Part)', |
|
||||
'Saint Pierre and Miquelon', |
|
||||
'Saint Vincent and the Grenadines', |
|
||||
'Samoa', |
|
||||
'San Marino', |
|
||||
'Sao Tome and Principe', |
|
||||
'Saudi Arabia', |
|
||||
'Senegal', |
|
||||
'Serbia', |
|
||||
'Seychelles', |
|
||||
'Sierra Leone', |
|
||||
'Singapore', |
|
||||
'Sint Maarten (Dutch Part)', |
|
||||
'Slovakia', |
|
||||
'Slovenia', |
|
||||
'Solomon Islands', |
|
||||
'Somalia', |
|
||||
'South Africa', |
|
||||
'South Georgia and the South Sandwich Islands', |
|
||||
'South Sudan', |
|
||||
'Spain', |
|
||||
'Sri Lanka', |
|
||||
'Sudan', |
|
||||
'Suriname', |
|
||||
'Svalbard and Jan Mayen', |
|
||||
'Swaziland', |
|
||||
'Sweden', |
|
||||
'Switzerland', |
|
||||
'Syrian Arab Republic', |
|
||||
'Taiwan', |
|
||||
'Tajikistan', |
|
||||
'Tanzania, United Republic of', |
|
||||
'Thailand', |
|
||||
'Timor-Leste', |
|
||||
'Togo', |
|
||||
'Tokelau', |
|
||||
'Tonga', |
|
||||
'Trinidad and Tobago', |
|
||||
'Tunisia', |
|
||||
'Turkey', |
|
||||
'Turkmenistan', |
|
||||
'Turks and Caicos Islands', |
|
||||
'Tuvalu', |
|
||||
'Uganda', |
|
||||
'Ukraine', |
|
||||
'United Arab Emirates', |
|
||||
'United Kingdom', |
|
||||
'United States', |
|
||||
'United States Minor Outlying Islands', |
|
||||
'Uruguay', |
|
||||
'Uzbekistan', |
|
||||
'Vanuatu', |
|
||||
'Venezuela', |
|
||||
'Viet Nam', |
|
||||
'Virgin Islands, British', |
|
||||
'Virgin Islands, U.S.', |
|
||||
'Wallis and Futuna', |
|
||||
'Western Sahara', |
|
||||
'Yemen', |
|
||||
'Zambia', |
|
||||
'Zimbabwe' |
|
||||
]; |
|
||||
|
|
||||
/* eslint-disable */ |
|
||||
export const POSTAL_CODE_PATTERNS = { |
|
||||
'United States': '(\\d{5}([\\-]\\d{4})?)', |
|
||||
'Australia': '[0-9]{4}', |
|
||||
'Austria': '[0-9]{4}', |
|
||||
'Belgium': '[0-9]{4}', |
|
||||
'Brazil': '[0-9]{5}[\\-]?[0-9]{3}', |
|
||||
'Canada': '^(?!.*[DFIOQU])[A-VXY][0-9][A-Z][ -]?[0-9][A-Z][0-9]$', |
|
||||
'Denmark': '[0-9]{3,4}', |
|
||||
'Faroe Islands': '[0-9]{3,4}', |
|
||||
'Netherlands': '[1-9][0-9]{3}\\s?[a-zA-Z]{2}', |
|
||||
'Germany': '[0-9]{5}', |
|
||||
'Hungary': '[0-9]{4}', |
|
||||
'Italy': '[0-9]{5}', |
|
||||
'Japan': '\\d{3}-\\d{4}', |
|
||||
'Luxembourg': '(L\\s*(-|—|–))\\s*?[\\d]{4}', |
|
||||
'Poland': '[0-9]{2}\\-[0-9]{3}', |
|
||||
'Spain': '((0[1-9]|5[0-2])|[1-4][0-9])[0-9]{3}', |
|
||||
'Sweden': '\\d{3}\\s?\\d{2}', |
|
||||
'United Kingdom': '[A-Za-z]{1,2}[0-9Rr][0-9A-Za-z]? [0-9][ABD-HJLNP-UW-Zabd-hjlnp-uw-z]{2}' |
|
||||
}; |
|
||||
/* eslint-enable */ |
|
||||
|
|
||||
@ -0,0 +1,58 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2024 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 [formGroup]="countryFormGroup" class="mat-block" subscriptSizing="{{subscriptSizing}}"> |
||||
|
<mat-label>{{ labelText }}</mat-label> |
||||
|
<input matInput type="text" |
||||
|
#countryInput |
||||
|
formControlName="country" |
||||
|
(focusin)="onFocus()" |
||||
|
(blur)="onTouched()" |
||||
|
[required]="required" |
||||
|
[matAutocomplete]="countryAutocomplete"> |
||||
|
<button *ngIf="countryFormGroup.get('country').value && !disabled" |
||||
|
type="button" |
||||
|
matSuffix mat-icon-button aria-label="Clear" |
||||
|
(click)="clear()"> |
||||
|
<mat-icon class="material-icons">close</mat-icon> |
||||
|
</button> |
||||
|
<mat-autocomplete class="tb-autocomplete" |
||||
|
#countryAutocomplete="matAutocomplete" |
||||
|
[displayWith]="displayCountryFn"> |
||||
|
<mat-option *ngFor="let country of filteredCountries | async" [value]="country"> |
||||
|
<span style="font-size: 20px;padding-right: 4px;">{{country.flag}}</span> |
||||
|
<span [innerHTML]="country.name | highlight:searchText"></span> |
||||
|
</mat-option> |
||||
|
<mat-option *ngIf="!(filteredCountries | async)?.length" [value]="null" class="tb-not-found"> |
||||
|
<div class="tb-not-found-content" (click)="$event.stopPropagation()"> |
||||
|
<div *ngIf="!textIsNotEmpty(searchText); else searchNotEmpty"> |
||||
|
<span translate>contact.no-country-found</span> |
||||
|
</div> |
||||
|
<ng-template #searchNotEmpty> |
||||
|
<span> |
||||
|
{{ 'contact.no-country-matching' | translate: |
||||
|
{country: (searchText | truncate: true: 6: '...')} }} |
||||
|
</span> |
||||
|
</ng-template> |
||||
|
</div> |
||||
|
</mat-option> |
||||
|
</mat-autocomplete> |
||||
|
<mat-hint *ngIf="autocompleteHint">{{ autocompleteHint }}</mat-hint> |
||||
|
<mat-error *ngIf="countryFormGroup.get('country').hasError('required')"> |
||||
|
{{ requiredText }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
@ -0,0 +1,209 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2024 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, EventEmitter, forwardRef, Input, OnInit, Output, ViewChild } from '@angular/core'; |
||||
|
import { Country, CountryData } from '@shared/models/country.models'; |
||||
|
import { |
||||
|
ControlValueAccessor, |
||||
|
FormBuilder, |
||||
|
FormGroup, |
||||
|
NG_VALIDATORS, |
||||
|
NG_VALUE_ACCESSOR, |
||||
|
ValidationErrors, |
||||
|
Validator |
||||
|
} from '@angular/forms'; |
||||
|
import { isNotEmptyStr } from '@core/utils'; |
||||
|
import { Observable, of } from 'rxjs'; |
||||
|
import { debounceTime, distinctUntilChanged, map, share, switchMap, tap } from 'rxjs/operators'; |
||||
|
import { SubscriptSizing } from '@angular/material/form-field'; |
||||
|
import { coerceBoolean } from '@shared/decorators/coercion'; |
||||
|
import { TranslateService } from '@ngx-translate/core'; |
||||
|
|
||||
|
interface CountrySearchData extends Country { |
||||
|
searchText?: string; |
||||
|
} |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-country-autocomplete', |
||||
|
templateUrl: 'country-autocomplete.component.html', |
||||
|
providers: [ |
||||
|
CountryData, |
||||
|
{ |
||||
|
provide: NG_VALUE_ACCESSOR, |
||||
|
useExisting: forwardRef(() => CountryAutocompleteComponent), |
||||
|
multi: true |
||||
|
}, |
||||
|
{ |
||||
|
provide: NG_VALIDATORS, |
||||
|
useExisting: forwardRef(() => CountryAutocompleteComponent), |
||||
|
multi: true |
||||
|
} |
||||
|
] |
||||
|
}) |
||||
|
export class CountryAutocompleteComponent implements OnInit, ControlValueAccessor, Validator { |
||||
|
|
||||
|
@Input() |
||||
|
labelText = this.translate.instant('contact.country'); |
||||
|
|
||||
|
@Input() |
||||
|
requiredText = this.translate.instant('contact.country-required'); |
||||
|
|
||||
|
@Input() |
||||
|
autocompleteHint: string; |
||||
|
|
||||
|
@Input() |
||||
|
disabled: boolean; |
||||
|
|
||||
|
@Input() |
||||
|
@coerceBoolean() |
||||
|
required = false; |
||||
|
|
||||
|
@Input() |
||||
|
subscriptSizing: SubscriptSizing = 'fixed'; |
||||
|
|
||||
|
@ViewChild('countryInput', {static: true}) countryInput: ElementRef; |
||||
|
|
||||
|
@Output() |
||||
|
selectCountryCode = new EventEmitter<string>(); |
||||
|
|
||||
|
countryFormGroup: FormGroup; |
||||
|
|
||||
|
searchText = ''; |
||||
|
|
||||
|
filteredCountries: Observable<Array<Country>>; |
||||
|
|
||||
|
onTouched = () => { |
||||
|
}; |
||||
|
private propagateChange: (value: any) => void = () => { |
||||
|
}; |
||||
|
|
||||
|
private modelValue: Country; |
||||
|
|
||||
|
private allCountries: CountrySearchData[] = this.countryData.allCountries; |
||||
|
private initSearchData = false; |
||||
|
private dirty = false; |
||||
|
|
||||
|
constructor(private fb: FormBuilder, |
||||
|
private countryData: CountryData, |
||||
|
private translate: TranslateService) { |
||||
|
this.countryFormGroup = this.fb.group({ |
||||
|
country: [''] |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
this.filteredCountries = this.countryFormGroup.get('country').valueChanges.pipe( |
||||
|
debounceTime(150), |
||||
|
tap(value => { |
||||
|
let modelValue: Country; |
||||
|
if (typeof value === 'string' || !value) { |
||||
|
modelValue = null; |
||||
|
} else { |
||||
|
modelValue = value; |
||||
|
} |
||||
|
this.updateView(modelValue); |
||||
|
if (value === null) { |
||||
|
this.clear(); |
||||
|
} |
||||
|
}), |
||||
|
map(value => value ? (typeof value === 'string' ? value : value.name) : ''), |
||||
|
distinctUntilChanged(), |
||||
|
switchMap(name => of(this.fetchCountries(name))), |
||||
|
share() |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
registerOnChange(fn: any): void { |
||||
|
this.propagateChange = fn; |
||||
|
} |
||||
|
|
||||
|
registerOnTouched(fn: any): void { |
||||
|
this.onTouched = fn; |
||||
|
} |
||||
|
|
||||
|
setDisabledState(isDisabled: boolean): void { |
||||
|
this.disabled = isDisabled; |
||||
|
if (isDisabled) { |
||||
|
this.countryFormGroup.disable({emitEvent: false}); |
||||
|
} else { |
||||
|
this.countryFormGroup.enable({emitEvent: false}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
validate(): ValidationErrors | null { |
||||
|
return this.countryFormGroup.valid ? null : { |
||||
|
countryFormGroup: false |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
writeValue(country: string) { |
||||
|
this.dirty = true; |
||||
|
|
||||
|
const findCountry = isNotEmptyStr(country) ? this.allCountries.find(item => item.name === country) : null; |
||||
|
|
||||
|
this.modelValue = findCountry || null; |
||||
|
this.countryFormGroup.get('country').patchValue(this.modelValue || '', { emitEvent: false }); |
||||
|
this.selectCountryCode.emit(this.modelValue ? this.modelValue.iso2 : null); |
||||
|
} |
||||
|
|
||||
|
displayCountryFn(country?: Country): string | undefined { |
||||
|
return country ? `${country.flag} ${country.name}` : undefined; |
||||
|
} |
||||
|
|
||||
|
onFocus() { |
||||
|
if (this.dirty) { |
||||
|
this.countryFormGroup.get('country').updateValueAndValidity({onlySelf: true}); |
||||
|
this.dirty = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
textIsNotEmpty(text: string): boolean { |
||||
|
return (text && text.length > 0); |
||||
|
} |
||||
|
|
||||
|
clear() { |
||||
|
this.countryFormGroup.get('country').patchValue('', {emitEvent: true}); |
||||
|
setTimeout(() => { |
||||
|
this.countryInput.nativeElement.blur(); |
||||
|
this.countryInput.nativeElement.focus(); |
||||
|
}, 0); |
||||
|
} |
||||
|
|
||||
|
private fetchCountries(searchText: string): Country[] { |
||||
|
this.searchText = searchText; |
||||
|
if (!this.initSearchData) { |
||||
|
this.allCountries.forEach(country => { |
||||
|
country.searchText = `${country.name} ${country.iso2}`.toLowerCase(); |
||||
|
}); |
||||
|
this.initSearchData = true; |
||||
|
} |
||||
|
if (isNotEmptyStr(searchText)) { |
||||
|
const filterValue = searchText.toLowerCase(); |
||||
|
return this.allCountries.filter(country => country.searchText.includes(filterValue)); |
||||
|
} |
||||
|
return this.allCountries; |
||||
|
} |
||||
|
|
||||
|
private updateView(value: Country | null) { |
||||
|
if (this.modelValue?.name !== value?.name) { |
||||
|
this.modelValue = value; |
||||
|
this.propagateChange(this.modelValue); |
||||
|
if (value) { |
||||
|
this.selectCountryCode.emit(value.iso2); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue