Add search addresses from cross networks feature
This commit is contained in:
		
							parent
							
								
									bd34d71d8b
								
							
						
					
					
						commit
						3e1b85e32c
					
				| @ -2,14 +2,14 @@ import { Component, OnInit, ChangeDetectionStrategy, EventEmitter, Output, ViewC | |||||||
| import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; | import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; | ||||||
| import { EventType, NavigationStart, Router } from '@angular/router'; | import { EventType, NavigationStart, Router } from '@angular/router'; | ||||||
| import { AssetsService } from '../../services/assets.service'; | import { AssetsService } from '../../services/assets.service'; | ||||||
| import { StateService } from '../../services/state.service'; | import { Env, StateService } from '../../services/state.service'; | ||||||
| import { Observable, of, Subject, zip, BehaviorSubject, combineLatest } from 'rxjs'; | import { Observable, of, Subject, zip, BehaviorSubject, combineLatest } from 'rxjs'; | ||||||
| import { debounceTime, distinctUntilChanged, switchMap, catchError, map, startWith,  tap } from 'rxjs/operators'; | import { debounceTime, distinctUntilChanged, switchMap, catchError, map, startWith,  tap } from 'rxjs/operators'; | ||||||
| import { ElectrsApiService } from '../../services/electrs-api.service'; | import { ElectrsApiService } from '../../services/electrs-api.service'; | ||||||
| import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe'; | import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe'; | ||||||
| import { ApiService } from '../../services/api.service'; | import { ApiService } from '../../services/api.service'; | ||||||
| import { SearchResultsComponent } from './search-results/search-results.component'; | import { SearchResultsComponent } from './search-results/search-results.component'; | ||||||
| import { findOtherNetworks, getRegex } from '../../shared/regex.utils'; | import { Network, findOtherNetworks, getRegex, getTargetUrl, needBaseModuleChange } from '../../shared/regex.utils'; | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-search-form', |   selector: 'app-search-form', | ||||||
| @ -19,7 +19,7 @@ import { findOtherNetworks, getRegex } from '../../shared/regex.utils'; | |||||||
| }) | }) | ||||||
| export class SearchFormComponent implements OnInit { | export class SearchFormComponent implements OnInit { | ||||||
|   @Input() hamburgerOpen = false; |   @Input() hamburgerOpen = false; | ||||||
|    |   env: Env; | ||||||
|   network = ''; |   network = ''; | ||||||
|   assets: object = {}; |   assets: object = {}; | ||||||
|   isSearching = false; |   isSearching = false; | ||||||
| @ -68,6 +68,7 @@ export class SearchFormComponent implements OnInit { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   ngOnInit(): void { |   ngOnInit(): void { | ||||||
|  |     this.env = this.stateService.env; | ||||||
|     this.stateService.networkChanged$.subscribe((network) => { |     this.stateService.networkChanged$.subscribe((network) => { | ||||||
|       this.network = network; |       this.network = network; | ||||||
|       // TODO: Eventually change network type here from string to enum of consts
 |       // TODO: Eventually change network type here from string to enum of consts
 | ||||||
| @ -103,9 +104,6 @@ export class SearchFormComponent implements OnInit { | |||||||
|     const searchText$ = this.searchForm.get('searchText').valueChanges |     const searchText$ = this.searchForm.get('searchText').valueChanges | ||||||
|     .pipe( |     .pipe( | ||||||
|       map((text) => { |       map((text) => { | ||||||
|         if (this.network === 'bisq' && text.match(/^(b)[^c]/i)) { |  | ||||||
|           return text.substr(1); |  | ||||||
|         } |  | ||||||
|         return text.trim(); |         return text.trim(); | ||||||
|       }), |       }), | ||||||
|       tap((text) => { |       tap((text) => { | ||||||
| @ -139,9 +137,6 @@ export class SearchFormComponent implements OnInit { | |||||||
|         ); |         ); | ||||||
|       }), |       }), | ||||||
|       map((result: any[]) => { |       map((result: any[]) => { | ||||||
|         if (this.network === 'bisq') { |  | ||||||
|           result[0] = result[0].map((address: string) => 'B' + address); |  | ||||||
|         } |  | ||||||
|         return result; |         return result; | ||||||
|       }), |       }), | ||||||
|       tap(() => { |       tap(() => { | ||||||
| @ -171,6 +166,7 @@ export class SearchFormComponent implements OnInit { | |||||||
|               blockHeight: false, |               blockHeight: false, | ||||||
|               txId: false, |               txId: false, | ||||||
|               address: false, |               address: false, | ||||||
|  |               otherNetworks: [], | ||||||
|               addresses: [], |               addresses: [], | ||||||
|               nodes: [], |               nodes: [], | ||||||
|               channels: [], |               channels: [], | ||||||
| @ -186,10 +182,13 @@ export class SearchFormComponent implements OnInit { | |||||||
|           const matchesUnixTimestamp = this.regexUnixTimestamp.test(searchText) && parseInt(searchText) <= Math.floor(Date.now() / 1000) && parseInt(searchText) >= 1231006505; // 1231006505 is the timestamp of the genesis block
 |           const matchesUnixTimestamp = this.regexUnixTimestamp.test(searchText) && parseInt(searchText) <= Math.floor(Date.now() / 1000) && parseInt(searchText) >= 1231006505; // 1231006505 is the timestamp of the genesis block
 | ||||||
|           const matchesTxId = this.regexTransaction.test(searchText) && !this.regexBlockhash.test(searchText); |           const matchesTxId = this.regexTransaction.test(searchText) && !this.regexBlockhash.test(searchText); | ||||||
|           const matchesBlockHash = this.regexBlockhash.test(searchText); |           const matchesBlockHash = this.regexBlockhash.test(searchText); | ||||||
|           const matchesAddress = !matchesTxId && this.regexAddress.test(searchText); |           let matchesAddress = !matchesTxId && this.regexAddress.test(searchText); | ||||||
|  |           const otherNetworks = findOtherNetworks(searchText, this.network as any || 'mainnet'); | ||||||
| 
 | 
 | ||||||
|           if (matchesAddress && this.network === 'bisq') { |           // Add B prefix to addresses in Bisq network
 | ||||||
|  |           if (!matchesAddress && this.network === 'bisq' && getRegex('address', 'mainnet').test(searchText)) { | ||||||
|               searchText = 'B' + searchText; |               searchText = 'B' + searchText; | ||||||
|  |               matchesAddress = !matchesTxId && this.regexAddress.test(searchText); | ||||||
|           } |           } | ||||||
| 
 | 
 | ||||||
|           if (matchesDateTime && searchText.indexOf('/') !== -1) { |           if (matchesDateTime && searchText.indexOf('/') !== -1) { | ||||||
| @ -205,7 +204,8 @@ export class SearchFormComponent implements OnInit { | |||||||
|             txId: matchesTxId, |             txId: matchesTxId, | ||||||
|             blockHash: matchesBlockHash, |             blockHash: matchesBlockHash, | ||||||
|             address: matchesAddress, |             address: matchesAddress, | ||||||
|             addresses: addressPrefixSearchResults, |             addresses: matchesAddress && addressPrefixSearchResults.length === 1 && searchText === addressPrefixSearchResults[0] ? [] : addressPrefixSearchResults, // If there is only one address and it matches the search text, don't show it in the dropdown
 | ||||||
|  |             otherNetworks: otherNetworks, | ||||||
|             nodes: lightningResults.nodes, |             nodes: lightningResults.nodes, | ||||||
|             channels: lightningResults.channels, |             channels: lightningResults.channels, | ||||||
|           }; |           }; | ||||||
| @ -230,6 +230,8 @@ export class SearchFormComponent implements OnInit { | |||||||
|       this.navigate('/lightning/node/', result.public_key); |       this.navigate('/lightning/node/', result.public_key); | ||||||
|     } else if (result.short_id) { |     } else if (result.short_id) { | ||||||
|       this.navigate('/lightning/channel/', result.id); |       this.navigate('/lightning/channel/', result.id); | ||||||
|  |     } else if (result.network) { | ||||||
|  |       this.navigate('/address/', result.address, undefined, result.network); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -238,12 +240,8 @@ export class SearchFormComponent implements OnInit { | |||||||
|     if (searchText) { |     if (searchText) { | ||||||
|       this.isSearching = true; |       this.isSearching = true; | ||||||
| 
 | 
 | ||||||
|       const otherNetworks = findOtherNetworks(searchText, this.network as any || 'mainnet'); |  | ||||||
|       if (!this.regexTransaction.test(searchText) && this.regexAddress.test(searchText)) { |       if (!this.regexTransaction.test(searchText) && this.regexAddress.test(searchText)) { | ||||||
|         this.navigate('/address/', searchText); |         this.navigate('/address/', searchText); | ||||||
|       } else if (otherNetworks.length > 0) { |  | ||||||
|         // Change the network to the first match
 |  | ||||||
|         this.navigate('/address/', searchText, undefined, otherNetworks[0]); |  | ||||||
|       } else if (this.regexBlockhash.test(searchText)) { |       } else if (this.regexBlockhash.test(searchText)) { | ||||||
|         this.navigate('/block/', searchText); |         this.navigate('/block/', searchText); | ||||||
|       } else if (this.regexBlockheight.test(searchText)) { |       } else if (this.regexBlockheight.test(searchText)) { | ||||||
| @ -288,6 +286,9 @@ export class SearchFormComponent implements OnInit { | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|   navigate(url: string, searchText: string, extras?: any, swapNetwork?: string) { |   navigate(url: string, searchText: string, extras?: any, swapNetwork?: string) { | ||||||
|  |     if (needBaseModuleChange(this.env.BASE_MODULE as 'liquid' | 'bisq' | 'mempool', swapNetwork as Network)) { | ||||||
|  |       window.location.href = getTargetUrl(swapNetwork as Network, searchText, this.env); | ||||||
|  |     } else { | ||||||
|       this.router.navigate([this.relativeUrlPipe.transform(url, swapNetwork), searchText], extras); |       this.router.navigate([this.relativeUrlPipe.transform(url, swapNetwork), searchText], extras); | ||||||
|       this.searchTriggered.emit(); |       this.searchTriggered.emit(); | ||||||
|       this.searchForm.setValue({ |       this.searchForm.setValue({ | ||||||
| @ -296,3 +297,4 @@ export class SearchFormComponent implements OnInit { | |||||||
|       this.isSearching = false; |       this.isSearching = false; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  | } | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| <div class="dropdown-menu show" *ngIf="results" [hidden]="!results.hashQuickMatch && !results.addresses.length && !results.nodes.length && !results.channels.length"> | <div class="dropdown-menu show" *ngIf="results" [hidden]="!results.hashQuickMatch && !results.otherNetworks.length && !results.addresses.length && !results.nodes.length && !results.channels.length"> | ||||||
|   <ng-template [ngIf]="results.blockHeight"> |   <ng-template [ngIf]="results.blockHeight"> | ||||||
|     <div class="card-title" i18n="search.bitcoin-block-height">Bitcoin Block Height</div> |     <div class="card-title" i18n="search.bitcoin-block-height">Bitcoin Block Height</div> | ||||||
|     <button (click)="clickItem(0)" [class.active]="0 === activeIdx" type="button" role="option" class="dropdown-item"> |     <button (click)="clickItem(0)" [class.active]="0 === activeIdx" type="button" role="option" class="dropdown-item"> | ||||||
| @ -35,10 +35,18 @@ | |||||||
|       <ng-container *ngTemplateOutlet="goTo; context: { $implicit: results.searchText | shortenString : 13 }"></ng-container> |       <ng-container *ngTemplateOutlet="goTo; context: { $implicit: results.searchText | shortenString : 13 }"></ng-container> | ||||||
|     </button> |     </button> | ||||||
|   </ng-template> |   </ng-template> | ||||||
|  |   <ng-template [ngIf]="results.otherNetworks.length"> | ||||||
|  |     <div class="card-title danger" i18n="search.other-networks">Other Networks Address</div> | ||||||
|  |     <ng-template ngFor [ngForOf]="results.otherNetworks" let-otherNetwork let-i="index"> | ||||||
|  |       <button (click)="clickItem(results.hashQuickMatch + i)" [class.active]="(results.hashQuickMatch + i) === activeIdx" type="button" role="option" class="dropdown-item"> | ||||||
|  |         <ngb-highlight [result]="(otherNetwork.address + ' (' + otherNetwork.network + ')') | shortenString : isMobile ? 25 : 36" [term]="otherNetwork.network"></ngb-highlight> | ||||||
|  |       </button> | ||||||
|  |     </ng-template> | ||||||
|  |   </ng-template> | ||||||
|   <ng-template [ngIf]="results.addresses.length"> |   <ng-template [ngIf]="results.addresses.length"> | ||||||
|     <div class="card-title" i18n="search.bitcoin-addresses">Bitcoin Addresses</div> |     <div class="card-title" i18n="search.bitcoin-addresses">Bitcoin Addresses</div> | ||||||
|     <ng-template ngFor [ngForOf]="results.addresses" let-address let-i="index"> |     <ng-template ngFor [ngForOf]="results.addresses" let-address let-i="index"> | ||||||
|       <button (click)="clickItem(results.hashQuickMatch + i)" [class.active]="(results.hashQuickMatch + i) === activeIdx" type="button" role="option" class="dropdown-item"> |       <button (click)="clickItem(results.hashQuickMatch + results.otherNetworks.length + i)" [class.active]="(results.hashQuickMatch + results.otherNetworks.length + i) === activeIdx" type="button" role="option" class="dropdown-item"> | ||||||
|         <ngb-highlight [result]="address | shortenString : isMobile ? 25 : 36" [term]="results.searchText"></ngb-highlight> |         <ngb-highlight [result]="address | shortenString : isMobile ? 25 : 36" [term]="results.searchText"></ngb-highlight> | ||||||
|       </button> |       </button> | ||||||
|     </ng-template> |     </ng-template> | ||||||
| @ -46,7 +54,7 @@ | |||||||
|   <ng-template [ngIf]="results.nodes.length"> |   <ng-template [ngIf]="results.nodes.length"> | ||||||
|     <div class="card-title" i18n="search.lightning-nodes">Lightning Nodes</div> |     <div class="card-title" i18n="search.lightning-nodes">Lightning Nodes</div> | ||||||
|     <ng-template ngFor [ngForOf]="results.nodes" let-node let-i="index"> |     <ng-template ngFor [ngForOf]="results.nodes" let-node let-i="index"> | ||||||
|       <button (click)="clickItem(results.hashQuickMatch + results.addresses.length + i)" [class.inactive]="node.status === 0" [class.active]="results.hashQuickMatch + results.addresses.length + i === activeIdx" [routerLink]="['/lightning/node' | relativeUrl, node.public_key]" type="button" role="option" class="dropdown-item"> |       <button (click)="clickItem(results.hashQuickMatch + results.otherNetworks.length + results.addresses.length + i)" [class.inactive]="node.status === 0" [class.active]="results.hashQuickMatch + results.otherNetworks.length + results.addresses.length + i === activeIdx" [routerLink]="['/lightning/node' | relativeUrl, node.public_key]" type="button" role="option" class="dropdown-item"> | ||||||
|         <ngb-highlight [result]="node.alias" [term]="results.searchText"></ngb-highlight>  <span class="symbol">{{ node.public_key | shortenString : 10 }}</span> |         <ngb-highlight [result]="node.alias" [term]="results.searchText"></ngb-highlight>  <span class="symbol">{{ node.public_key | shortenString : 10 }}</span> | ||||||
|       </button> |       </button> | ||||||
|     </ng-template> |     </ng-template> | ||||||
| @ -54,7 +62,7 @@ | |||||||
|   <ng-template [ngIf]="results.channels.length"> |   <ng-template [ngIf]="results.channels.length"> | ||||||
|     <div class="card-title" i18n="search.lightning-channels">Lightning Channels</div> |     <div class="card-title" i18n="search.lightning-channels">Lightning Channels</div> | ||||||
|     <ng-template ngFor [ngForOf]="results.channels" let-channel let-i="index"> |     <ng-template ngFor [ngForOf]="results.channels" let-channel let-i="index"> | ||||||
|       <button (click)="clickItem(results.hashQuickMatch + results.addresses.length + results.nodes.length + i)" [class.inactive]="channel.status === 2"  [class.active]="results.hashQuickMatch + results.addresses.length + results.nodes.length + i === activeIdx" type="button" role="option" class="dropdown-item"> |       <button (click)="clickItem(results.hashQuickMatch + results.otherNetworks.length + results.addresses.length + results.nodes.length + i)" [class.inactive]="channel.status === 2"  [class.active]="results.hashQuickMatch + results.otherNetworks.length + results.addresses.length + results.nodes.length + i === activeIdx" type="button" role="option" class="dropdown-item"> | ||||||
|         <ngb-highlight [result]="channel.short_id" [term]="results.searchText"></ngb-highlight>  <span class="symbol">{{ channel.id }}</span> |         <ngb-highlight [result]="channel.short_id" [term]="results.searchText"></ngb-highlight>  <span class="symbol">{{ channel.id }}</span> | ||||||
|       </button> |       </button> | ||||||
|     </ng-template> |     </ng-template> | ||||||
|  | |||||||
| @ -7,6 +7,10 @@ | |||||||
|   margin-left: 10px; |   margin-left: 10px; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .danger { | ||||||
|  |   color: #dc3545; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .dropdown-menu { | .dropdown-menu { | ||||||
|   position: absolute; |   position: absolute; | ||||||
|   top: 42px; |   top: 42px; | ||||||
|  | |||||||
| @ -22,7 +22,7 @@ export class SearchResultsComponent implements OnChanges { | |||||||
|   ngOnChanges() { |   ngOnChanges() { | ||||||
|     this.activeIdx = 0; |     this.activeIdx = 0; | ||||||
|     if (this.results) { |     if (this.results) { | ||||||
|       this.resultsFlattened = [...(this.results.hashQuickMatch ? [this.results.searchText] : []), ...this.results.addresses, ...this.results.nodes, ...this.results.channels]; |       this.resultsFlattened = [...(this.results.hashQuickMatch ? [this.results.searchText] : []), ...this.results.otherNetworks, ...this.results.addresses, ...this.results.nodes, ...this.results.channels]; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,3 +1,5 @@ | |||||||
|  | import { Env } from '../services/state.service'; | ||||||
|  | 
 | ||||||
| // all base58 characters
 | // all base58 characters
 | ||||||
| const BASE58_CHARS = `[a-km-zA-HJ-NP-Z1-9]`; | const BASE58_CHARS = `[a-km-zA-HJ-NP-Z1-9]`; | ||||||
| 
 | 
 | ||||||
| @ -148,6 +150,41 @@ export function findOtherNetworks(address: string, skipNetwork: Network): {netwo | |||||||
|     .map(([, network]) => ({ network, address })); |     .map(([, network]) => ({ network, address })); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | export function needBaseModuleChange(fromBaseModule: 'mempool' | 'liquid' | 'bisq', toNetwork: Network): boolean { | ||||||
|  |   if (!toNetwork) return false; // No target network means no change needed
 | ||||||
|  |   if (fromBaseModule === 'mempool') { | ||||||
|  |     return toNetwork !== 'mainnet' && toNetwork !== 'testnet' && toNetwork !== 'signet'; | ||||||
|  |   } | ||||||
|  |   if (fromBaseModule === 'liquid') { | ||||||
|  |     return toNetwork !== 'liquid' && toNetwork !== 'liquidtestnet'; | ||||||
|  |   } | ||||||
|  |   if (fromBaseModule === 'bisq') { | ||||||
|  |     return toNetwork !== 'bisq'; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function getTargetUrl(toNetwork: Network, address: string, env: Env): string { | ||||||
|  |   let targetUrl = ''; | ||||||
|  |   if (toNetwork === 'liquid' || toNetwork === 'liquidtestnet') { | ||||||
|  |     targetUrl = env.LIQUID_WEBSITE_URL; | ||||||
|  |     targetUrl += (toNetwork === 'liquidtestnet' ? '/testnet' : ''); | ||||||
|  |     targetUrl += '/address/'; | ||||||
|  |     targetUrl += address; | ||||||
|  |   } | ||||||
|  |   if (toNetwork === 'bisq') { | ||||||
|  |     targetUrl = env.BISQ_WEBSITE_URL; | ||||||
|  |     targetUrl += '/address/'; | ||||||
|  |     targetUrl += address; | ||||||
|  |   } | ||||||
|  |   if (toNetwork === 'mainnet' || toNetwork === 'testnet' || toNetwork === 'signet') { | ||||||
|  |     targetUrl = env.MEMPOOL_WEBSITE_URL; | ||||||
|  |     targetUrl += (toNetwork === 'mainnet' ? '' : `/${toNetwork}`); | ||||||
|  |     targetUrl += '/address/'; | ||||||
|  |     targetUrl += address; | ||||||
|  |   } | ||||||
|  |   return targetUrl; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| export function getRegex(type: RegexTypeNoAddrNoBlockHash): RegExp; | export function getRegex(type: RegexTypeNoAddrNoBlockHash): RegExp; | ||||||
| export function getRegex(type: 'address', network: Network): RegExp; | export function getRegex(type: 'address', network: Network): RegExp; | ||||||
| export function getRegex(type: 'blockhash', network: Network): RegExp; | export function getRegex(type: 'blockhash', network: Network): RegExp; | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user