diff --git a/frontend/src/app/components/assets/assets-nav/assets-nav.component.ts b/frontend/src/app/components/assets/assets-nav/assets-nav.component.ts index 10242bf61..c31e6cbaa 100644 --- a/frontend/src/app/components/assets/assets-nav/assets-nav.component.ts +++ b/frontend/src/app/components/assets/assets-nav/assets-nav.component.ts @@ -3,19 +3,13 @@ import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms import { Router } from '@angular/router'; import { NgbTypeahead } from '@ng-bootstrap/ng-bootstrap'; import { merge, Observable, of, Subject } from 'rxjs'; -import { distinctUntilChanged, filter, map, switchMap } from 'rxjs/operators'; +import { debounceTime, distinctUntilChanged, filter, switchMap } from 'rxjs/operators'; import { AssetsService } from '@app/services/assets.service'; import { SeoService } from '@app/services/seo.service'; import { StateService } from '@app/services/state.service'; import { RelativeUrlPipe } from '@app/shared/pipes/relative-url/relative-url.pipe'; import { environment } from '@environments/environment'; - -interface AssetSearchResult { - asset_id: string; - name: string; - ticker: string; - entity?: { domain: string }; -} +import { AssetRegistryItem } from '@interfaces/electrs.interface'; @Component({ selector: 'app-assets-nav', @@ -27,10 +21,10 @@ export class AssetsNavComponent implements OnInit { @ViewChild('instance', {static: true}) instance: NgbTypeahead; nativeAssetId = this.stateService.network === 'liquidtestnet' ? environment.nativeTestAssetId : environment.nativeAssetId; searchForm: UntypedFormGroup; - assetsCache: AssetSearchResult[]; + assetsCache: AssetRegistryItem[]; typeaheadSearchFn: ((text: Observable) => Observable); - formatterFn = (asset: AssetSearchResult) => asset.name + ' (' + asset.ticker + ')'; + formatterFn = (asset: AssetRegistryItem) => asset.name + ' (' + asset.ticker + ')'; focus$ = new Subject(); click$ = new Subject(); @@ -57,6 +51,7 @@ export class AssetsNavComponent implements OnInit { typeaheadSearch = (text$: Observable) => { const debouncedText$ = text$.pipe( + debounceTime(200), distinctUntilChanged() ); const clicksWithClosedPopup$ = this.click$.pipe(filter(() => !this.instance.isPopupOpen())); @@ -68,23 +63,7 @@ export class AssetsNavComponent implements OnInit { if (!searchText.length) { return of([]); } - return this.assetsService.getAssetsMinimalJson$.pipe( - map((assets) => { - if (searchText.length ) { - const filteredAssets = Object.entries(assets).map(([assetId, assetData]: [string, any[]]) => ({ - asset_id: assetId, - entity: assetData[0] ? { domain: assetData[0] } : undefined, - ticker: assetData[1] || '', - name: assetData[2] || '', - })).filter((asset) => asset.name.toLowerCase().indexOf(searchText.toLowerCase()) > -1 - || (asset.ticker || '').toLowerCase().indexOf(searchText.toLowerCase()) > -1 - || (asset.entity && asset.entity.domain || '').toLowerCase().indexOf(searchText.toLowerCase()) > -1); - return filteredAssets.slice(0, this.itemsPerPage); - } else { - return []; - } - }) - ); + return this.assetsService.searchLiquidAssets$(searchText, this.itemsPerPage); }), ); }; diff --git a/frontend/src/app/services/assets.service.ts b/frontend/src/app/services/assets.service.ts index 042d76531..687b2fe33 100644 --- a/frontend/src/app/services/assets.service.ts +++ b/frontend/src/app/services/assets.service.ts @@ -187,4 +187,32 @@ export class AssetsService { })), ); } + + public searchLiquidAssets$(searchText: string, limit: number): Observable { + const lowerSearchText = searchText.toLowerCase(); + return (this.registryAvailable ? this.electrsApiService.getLiquidAssetsRegistrySearch$(searchText).pipe( + catchError((error) => { + if (![404, 501].includes(error.status)) { + return throwError(() => error); + } + this.registryAvailable = false; + return of(null); + }), + ) : of(null)).pipe( + switchMap((registryAssets) => registryAssets ? of(registryAssets) : this.getAssetsMinimalJson$.pipe( + map((assets) => Object.entries(assets).map(([assetId, assetData]: [string, any[]]) => ({ + asset_id: assetId, + entity: assetData[0] ? { domain: assetData[0] } : undefined, + ticker: assetData[1] || '', + name: assetData[2] || '', + })).filter((asset) => asset.name.toLowerCase().indexOf(lowerSearchText) > -1 + || (asset.ticker || '').toLowerCase().indexOf(lowerSearchText) > -1 + || (asset.entity && asset.entity.domain || '').toLowerCase().indexOf(lowerSearchText) > -1)), + )), + map((assets) => assets.map((asset) => ({ + ...asset, + entity: asset.entity || (asset.domain ? { domain: asset.domain } : undefined), + })).slice(0, limit)), + ); + } } diff --git a/frontend/src/app/services/electrs-api.service.ts b/frontend/src/app/services/electrs-api.service.ts index 687156f44..c3d53f7da 100644 --- a/frontend/src/app/services/electrs-api.service.ts +++ b/frontend/src/app/services/electrs-api.service.ts @@ -238,6 +238,11 @@ export class ElectrsApiService { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/assets/registry', { params, observe: 'response' }); } + getLiquidAssetsRegistrySearch$(query: string): Observable { + const params = new HttpParams().set('q', query); + return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/assets/registry/search', { params }); + } + getAssetTransactions$(assetId: string): Observable { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/asset/' + assetId + '/txs'); }