2022-08-02 00:37:54 +00:00
import { Component , OnInit , OnDestroy , ViewChild , ElementRef } from '@angular/core' ;
import { ActivatedRoute , ParamMap } from '@angular/router' ;
import { ElectrsApiService } from '../../services/electrs-api.service' ;
2022-08-03 16:43:47 +00:00
import { switchMap , tap , throttleTime , catchError , shareReplay , startWith , pairwise , filter } from 'rxjs/operators' ;
2023-12-29 14:13:13 +00:00
import { of , Subscription , asyncScheduler , forkJoin } from 'rxjs' ;
2022-08-02 00:37:54 +00:00
import { StateService } from '../../services/state.service' ;
2022-09-21 17:23:45 +02:00
import { SeoService } from '../../services/seo.service' ;
import { OpenGraphService } from '../../services/opengraph.service' ;
import { BlockExtended , TransactionStripped } from '../../interfaces/node-api.interface' ;
import { ApiService } from '../../services/api.service' ;
2023-08-30 20:26:07 +09:00
import { seoDescriptionNetwork } from '../../shared/common.utils' ;
2022-09-21 17:23:45 +02:00
import { BlockOverviewGraphComponent } from '../../components/block-overview-graph/block-overview-graph.component' ;
2024-01-09 12:11:45 +01:00
import { ServicesApiServices } from '../../services/services-api.service' ;
2022-07-12 22:04:20 +00:00
@Component ({
selector : 'app-block-preview' ,
templateUrl : './block-preview.component.html' ,
2022-08-02 00:37:54 +00:00
styleUrls : [ './block-preview.component.scss' ]
2022-07-12 22:04:20 +00:00
})
2022-08-02 00:37:54 +00:00
export class BlockPreviewComponent implements OnInit , OnDestroy {
network = '' ;
block : BlockExtended ;
blockHeight : number ;
blockHash : string ;
2022-08-31 17:24:56 +00:00
rawId : string ;
2022-08-02 00:37:54 +00:00
isLoadingBlock = true ;
strippedTransactions : TransactionStripped [];
overviewTransitionDirection : string ;
isLoadingOverview = true ;
error : any ;
blockSubsidy : number ;
fees : number ;
overviewError : any = null ;
overviewSubscription : Subscription ;
networkChangedSubscription : Subscription ;
@ViewChild ( 'blockGraph' ) blockGraph : BlockOverviewGraphComponent ;
constructor (
private route : ActivatedRoute ,
private electrsApiService : ElectrsApiService ,
public stateService : StateService ,
private seoService : SeoService ,
private openGraphService : OpenGraphService ,
2024-01-09 12:11:45 +01:00
private apiService : ApiService ,
private servicesApiService : ServicesApiServices ,
2022-08-02 00:37:54 +00:00
) { }
ngOnInit() {
this . network = this . stateService . network ;
const block$ = this . route . paramMap . pipe (
switchMap (( params : ParamMap ) => {
2022-08-31 17:24:56 +00:00
this . rawId = params . get ( 'id' ) || '' ;
this . openGraphService . waitFor ( 'block-viz-' + this . rawId );
this . openGraphService . waitFor ( 'block-data-' + this . rawId );
2022-08-02 00:37:54 +00:00
const blockHash : string = params . get ( 'id' ) || '' ;
this . block = undefined ;
this . error = undefined ;
2022-08-03 16:43:47 +00:00
this . overviewError = undefined ;
2022-08-02 00:37:54 +00:00
this . fees = 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 ) => {
2022-08-03 16:43:47 +00:00
if ( hash ) {
this . blockHash = hash ;
return this . apiService . getBlock$ ( hash );
} else {
return null ;
}
}),
catchError (( err ) => {
this . error = err ;
2023-03-09 02:34:21 -06:00
this . seoService . logSoft404 ();
2022-08-31 17:24:56 +00:00
this . openGraphService . fail ( 'block-data-' + this . rawId );
this . openGraphService . fail ( 'block-viz-' + this . rawId );
2022-08-03 16:43:47 +00:00
return of ( null );
}),
2022-08-02 00:37:54 +00:00
);
}
return this . apiService . getBlock$ ( blockHash );
}),
2022-08-03 16:43:47 +00:00
filter (( block : BlockExtended | void ) => block != null ),
2022-08-02 00:37:54 +00:00
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:` );
2023-08-30 20:26:07 +09:00
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:).` );
}
2022-08-02 00:37:54 +00:00
this . isLoadingBlock = false ;
this . setBlockSubsidy ();
if ( block ? . extras ? . reward !== undefined ) {
this . fees = block . extras . reward / 100000000 - this . blockSubsidy ;
}
this . stateService . markBlock$ . next ({ blockHeight : this.blockHeight });
this . isLoadingOverview = true ;
this . overviewError = null ;
2022-08-31 17:24:56 +00:00
this . openGraphService . waitOver ( 'block-data-' + this . rawId );
2022-08-02 00:37:54 +00:00
}),
throttleTime ( 50 , asyncScheduler , { leading : true , trailing : true }),
shareReplay ( 1 )
);
this . overviewSubscription = block$ . pipe (
startWith ( null ),
pairwise (),
2023-12-29 14:13:13 +00:00
switchMap (([ prevBlock , block ]) => {
return forkJoin ([
this . apiService . getStrippedBlockTransactions$ ( block . id )
. pipe (
catchError (( err ) => {
this . overviewError = err ;
this . openGraphService . fail ( 'block-viz-' + this . rawId );
return of ([]);
}),
switchMap (( transactions ) => {
return of ( transactions );
})
),
2024-05-12 21:15:27 +00:00
this . stateService . env . ACCELERATOR === true && block . height > 819500
? this . servicesApiService . getAccelerationHistory$ ({ blockHeight : block.height })
. pipe ( catchError (() => {
return of ([]);
}))
: of ([])
2023-12-29 14:13:13 +00:00
]);
}
2022-08-02 00:37:54 +00:00
),
)
2023-12-29 14:13:13 +00:00
. subscribe (([ transactions , accelerations ]) => {
2022-08-02 00:37:54 +00:00
this . strippedTransactions = transactions ;
2023-12-29 14:13:13 +00:00
const acceleratedInBlock = {};
for ( const acc of accelerations ) {
acceleratedInBlock [ acc . txid ] = acc ;
}
for ( const tx of transactions ) {
if ( acceleratedInBlock [ tx . txid ]) {
tx . acc = true ;
}
}
2022-08-02 00:37:54 +00:00
this . isLoadingOverview = false ;
if ( this . blockGraph ) {
this . blockGraph . destroy ();
this . blockGraph . setup ( this . strippedTransactions );
}
},
( error ) => {
this . error = error ;
this . isLoadingOverview = false ;
2023-03-09 02:34:21 -06:00
this . seoService . logSoft404 ();
2022-08-31 17:24:56 +00:00
this . openGraphService . fail ( 'block-viz-' + this . rawId );
this . openGraphService . fail ( 'block-data-' + this . rawId );
2022-08-02 00:37:54 +00:00
if ( this . blockGraph ) {
this . blockGraph . destroy ();
}
});
this . networkChangedSubscription = this . stateService . networkChanged$
. subscribe (( network ) => this . network = network );
}
ngOnDestroy() {
if ( this . overviewSubscription ) {
this . overviewSubscription . unsubscribe ();
}
if ( this . networkChangedSubscription ) {
this . networkChangedSubscription . unsubscribe ();
}
}
// TODO - Refactor this.fees/this.reward for liquid because it is not
// used anymore on Bitcoin networks (we use block.extras directly)
setBlockSubsidy() {
this . blockSubsidy = 0 ;
}
onGraphReady () : void {
2022-08-31 17:24:56 +00:00
this . openGraphService . waitOver ( 'block-viz-' + this . rawId );
2022-08-02 00:37:54 +00:00
}
2022-07-12 22:04:20 +00:00
}