2023-09-21 21:57:54 +01:00
import { Component , OnInit , OnDestroy , ViewChild , HostListener } from '@angular/core' ;
2023-09-23 22:25:22 +01:00
import { ActivatedRoute , ParamMap , Router } from '@angular/router' ;
2023-09-21 21:57:54 +01:00
import { ElectrsApiService } from '../../services/electrs-api.service' ;
2023-09-23 22:25:22 +01:00
import { switchMap , tap , catchError , shareReplay , filter } from 'rxjs/operators' ;
import { of , Subscription } from 'rxjs' ;
2023-09-21 21:57:54 +01:00
import { StateService } from '../../services/state.service' ;
import { SeoService } from '../../services/seo.service' ;
import { BlockExtended , TransactionStripped } from '../../interfaces/node-api.interface' ;
import { ApiService } from '../../services/api.service' ;
import { seoDescriptionNetwork } from '../../shared/common.utils' ;
import { BlockOverviewGraphComponent } from '../block-overview-graph/block-overview-graph.component' ;
2023-09-23 22:25:22 +01:00
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe' ;
2023-09-21 21:57:54 +01:00
2023-09-23 22:25:22 +01:00
function bestFitResolution ( min , max , n ) : number {
2023-09-21 21:57:54 +01:00
const target = ( min + max ) / 2 ;
let bestScore = Infinity ;
let best = null ;
for ( let i = min ; i <= max ; i ++ ) {
const remainder = ( n % i ) ;
if ( remainder < bestScore || ( remainder === bestScore && ( Math . abs ( i - target ) < Math . abs ( best - target ) ) ) ) {
bestScore = remainder ;
best = i ;
}
}
return best ;
}
@Component ( {
selector : 'app-block-view' ,
templateUrl : './block-view.component.html' ,
styleUrls : [ './block-view.component.scss' ]
} )
export class BlockViewComponent implements OnInit , OnDestroy {
network = '' ;
block : BlockExtended ;
blockHeight : number ;
blockHash : string ;
rawId : string ;
isLoadingBlock = true ;
strippedTransactions : TransactionStripped [ ] ;
isLoadingOverview = true ;
autofit : boolean = false ;
resolution : number = 80 ;
overviewSubscription : Subscription ;
networkChangedSubscription : Subscription ;
queryParamsSubscription : Subscription ;
@ViewChild ( 'blockGraph' ) blockGraph : BlockOverviewGraphComponent ;
constructor (
private route : ActivatedRoute ,
2023-09-23 22:25:22 +01:00
private router : Router ,
2023-09-21 21:57:54 +01:00
private electrsApiService : ElectrsApiService ,
public stateService : StateService ,
private seoService : SeoService ,
private apiService : ApiService
) { }
2023-09-23 22:25:22 +01:00
ngOnInit ( ) : void {
2023-09-21 21:57:54 +01:00
this . network = this . stateService . network ;
this . queryParamsSubscription = this . route . queryParams . subscribe ( ( params ) = > {
this . autofit = params . autofit === 'true' ;
if ( this . autofit ) {
this . onResize ( ) ;
}
} ) ;
const block $ = this . route . paramMap . pipe (
switchMap ( ( params : ParamMap ) = > {
this . rawId = params . get ( 'id' ) || '' ;
const blockHash : string = params . get ( 'id' ) || '' ;
this . block = undefined ;
let isBlockHeight = false ;
if ( /^[0-9]+$/ . test ( blockHash ) ) {
isBlockHeight = true ;
} else {
this . blockHash = blockHash ;
}
this . isLoadingBlock = true ;
this . isLoadingOverview = true ;
if ( isBlockHeight ) {
return this . electrsApiService . getBlockHashFromHeight $ ( parseInt ( blockHash , 10 ) )
. pipe (
switchMap ( ( hash ) = > {
if ( hash ) {
this . blockHash = hash ;
return this . apiService . getBlock $ ( hash ) ;
} else {
return null ;
}
} ) ,
catchError ( ( ) = > {
return of ( null ) ;
} ) ,
) ;
}
return this . apiService . getBlock $ ( blockHash ) ;
} ) ,
filter ( ( block : BlockExtended | void ) = > block != null ) ,
tap ( ( block : BlockExtended ) = > {
this . block = block ;
this . blockHeight = block . height ;
this . seoService . setTitle ( $localize ` :@@block.component.browser-title:Block ${ block . height } :BLOCK_HEIGHT:: ${ block . id } :BLOCK_ID: ` ) ;
if ( this . stateService . network === 'liquid' || this . stateService . network === 'liquidtestnet' ) {
this . seoService . setDescription ( $localize ` :@@meta.description.liquid.block:See size, weight, fee range, included transactions, and more for Liquid ${ seoDescriptionNetwork ( this . stateService . network ) } block ${ block . height } :BLOCK_HEIGHT: ( ${ block . id } :BLOCK_ID:). ` ) ;
} else {
this . seoService . setDescription ( $localize ` :@@meta.description.bitcoin.block:See size, weight, fee range, included transactions, audit (expected v actual), and more for Bitcoin ${ seoDescriptionNetwork ( this . stateService . network ) } block ${ block . height } :BLOCK_HEIGHT: ( ${ block . id } :BLOCK_ID:). ` ) ;
}
this . isLoadingBlock = false ;
this . isLoadingOverview = true ;
} ) ,
shareReplay ( 1 )
) ;
this . overviewSubscription = block $ . pipe (
switchMap ( ( block ) = > this . apiService . getStrippedBlockTransactions $ ( block . id )
. pipe (
catchError ( ( ) = > {
return of ( [ ] ) ;
} ) ,
switchMap ( ( transactions ) = > {
return of ( transactions ) ;
} )
)
) ,
)
. subscribe ( ( transactions : TransactionStripped [ ] ) = > {
this . strippedTransactions = transactions ;
this . isLoadingOverview = false ;
if ( this . blockGraph ) {
this . blockGraph . destroy ( ) ;
this . blockGraph . setup ( this . strippedTransactions ) ;
}
} ,
( ) = > {
this . isLoadingOverview = false ;
if ( this . blockGraph ) {
this . blockGraph . destroy ( ) ;
}
} ) ;
this . networkChangedSubscription = this . stateService . networkChanged $
. subscribe ( ( network ) = > this . network = network ) ;
}
2023-09-23 22:25:22 +01:00
onTxClick ( event : { tx : TransactionStripped , keyModifier : boolean } ) : void {
const url = new RelativeUrlPipe ( this . stateService ) . transform ( ` /tx/ ${ event . tx . txid } ` ) ;
if ( ! event . keyModifier ) {
this . router . navigate ( [ url ] ) ;
} else {
window . open ( url , '_blank' ) ;
}
}
2023-09-21 21:57:54 +01:00
@HostListener ( 'window:resize' , [ '$event' ] )
onResize ( ) : void {
if ( this . autofit ) {
this . resolution = bestFitResolution ( 64 , 96 , Math . min ( window . innerWidth , window . innerHeight ) ) ;
}
}
2023-09-23 22:25:22 +01:00
ngOnDestroy ( ) : void {
2023-09-21 21:57:54 +01:00
if ( this . overviewSubscription ) {
this . overviewSubscription . unsubscribe ( ) ;
}
if ( this . networkChangedSubscription ) {
this . networkChangedSubscription . unsubscribe ( ) ;
}
if ( this . queryParamsSubscription ) {
this . queryParamsSubscription . unsubscribe ( ) ;
}
}
}