Refactor block transactions
This commit is contained in:
parent
0497c750be
commit
a68c8d317c
@ -0,0 +1,53 @@
|
|||||||
|
<div #blockTxTitle id="block-tx-title" class="block-tx-title">
|
||||||
|
<h2 class="text-left">
|
||||||
|
<ng-container *ngTemplateOutlet="txCount === 1 ? transactionsSingular : transactionsPlural; context: {$implicit: txCount | number}"></ng-container>
|
||||||
|
<ng-template #transactionsSingular let-i i18n="shared.transaction-count.singular">{{ i }} transaction</ng-template>
|
||||||
|
<ng-template #transactionsPlural let-i i18n="shared.transaction-count.plural">{{ i }} transactions</ng-template>
|
||||||
|
</h2>
|
||||||
|
<ngb-pagination class="pagination-container float-right" [collectionSize]="txCount" [rotate]="true" [pageSize]="itemsPerPage" [(page)]="page" (pageChange)="pageChange(page, blockTxTitle)" [maxSize]="paginationMaxSize" [boundaryLinks]="true" [ellipses]="false"></ngb-pagination>
|
||||||
|
</div>
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
|
||||||
|
<app-transactions-list *ngIf="transactions$ | async as transactions; else loading" [transactions]="transactions" [paginated]="true" [blockTime]="timestamp"></app-transactions-list>
|
||||||
|
|
||||||
|
<ng-template [ngIf]="transactionsError">
|
||||||
|
<br>
|
||||||
|
<app-http-error [error]="transactionsError">
|
||||||
|
<span i18n="error.general-loading-data">Error loading data.</span>
|
||||||
|
</app-http-error>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template #loading>
|
||||||
|
<div class="text-center mb-4" class="tx-skeleton">
|
||||||
|
<ng-container *ngIf="(txsLoadingStatus$ | async) as txsLoadingStatus; else headerLoader">
|
||||||
|
<div class="header-bg box">
|
||||||
|
<div class="progress progress-dark" style="margin: 4px; height: 14px;">
|
||||||
|
<div class="progress-bar progress-light" role="progressbar" [ngStyle]="{'width': txsLoadingStatus + '%' }"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<div class="header-bg box">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm">
|
||||||
|
<span class="skeleton-loader"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm">
|
||||||
|
<span class="skeleton-loader"></span>
|
||||||
|
<span class="skeleton-loader"></span>
|
||||||
|
<span class="skeleton-loader"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template #headerLoader>
|
||||||
|
<div class="header-bg box">
|
||||||
|
<span class="skeleton-loader"></span>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ngb-pagination class="pagination-container float-right" [collectionSize]="txCount" [rotate]="true" [pageSize]="itemsPerPage" [(page)]="page" (pageChange)="pageChange(page, blockTxTitle)" [maxSize]="paginationMaxSize" [boundaryLinks]="true" [ellipses]="false"></ngb-pagination>
|
@ -0,0 +1,37 @@
|
|||||||
|
.block-tx-title {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-top: -15px;
|
||||||
|
position: relative;
|
||||||
|
@media (min-width: 550px) {
|
||||||
|
margin-top: 1rem;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
line-height: 1;
|
||||||
|
margin: 0;
|
||||||
|
position: relative;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
@media (min-width: 550px) {
|
||||||
|
padding-bottom: 0px;
|
||||||
|
align-self: end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tx-skeleton {
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
.header-bg {
|
||||||
|
&:first-child {
|
||||||
|
padding: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
&:nth-child(2) {
|
||||||
|
.row {
|
||||||
|
height: 107px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,88 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
|
||||||
|
import { StateService } from '../../services/state.service';
|
||||||
|
import { Transaction, Vout } from '../../interfaces/electrs.interface';
|
||||||
|
import { Observable, Subscription, catchError, combineLatest, map, of, startWith, switchMap, tap } from 'rxjs';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { ElectrsApiService } from '../../services/electrs-api.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-block-transactions',
|
||||||
|
templateUrl: './block-transactions.component.html',
|
||||||
|
styleUrl: './block-transactions.component.scss',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class BlockTransactionsComponent implements OnInit, OnDestroy {
|
||||||
|
@Input() txCount: number;
|
||||||
|
@Input() timestamp: number;
|
||||||
|
@Input() blockHash: string;
|
||||||
|
@Input() previousBlockHash: string;
|
||||||
|
@Input() block$: Observable<any>;
|
||||||
|
@Input() paginationMaxSize: number;
|
||||||
|
@Output() blockReward = new EventEmitter<number>();
|
||||||
|
|
||||||
|
itemsPerPage = this.stateService.env.ITEMS_PER_PAGE;
|
||||||
|
page = 1;
|
||||||
|
|
||||||
|
transactions$: Observable<Transaction[]>;
|
||||||
|
isLoadingTransactions = true;
|
||||||
|
transactionsError: any = null;
|
||||||
|
transactionSubscription: Subscription;
|
||||||
|
txsLoadingStatus$: Observable<number>;
|
||||||
|
nextBlockTxListSubscription: Subscription;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private stateService: StateService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private router: Router,
|
||||||
|
private electrsApiService: ElectrsApiService,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.transactions$ = combineLatest([this.block$, this.route.queryParams]).pipe(
|
||||||
|
tap(([_, queryParams]) => {
|
||||||
|
this.page = +queryParams['page'] || 1;
|
||||||
|
}),
|
||||||
|
switchMap(([block, _]) => this.electrsApiService.getBlockTransactions$(block.id, (this.page - 1) * this.itemsPerPage)
|
||||||
|
.pipe(
|
||||||
|
startWith(null),
|
||||||
|
catchError((err) => {
|
||||||
|
this.transactionsError = err;
|
||||||
|
return of([]);
|
||||||
|
}))
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.pipe(
|
||||||
|
tap((transactions: Transaction[]) => {
|
||||||
|
if (transactions && transactions[0] && transactions[0].vin[0].is_coinbase) {
|
||||||
|
const blockReward = transactions[0].vout.reduce((acc: number, curr: Vout) => acc + curr.value, 0) / 100000000;
|
||||||
|
this.blockReward.emit(blockReward);
|
||||||
|
}
|
||||||
|
this.unsubscribeNextBlockSubscriptions();
|
||||||
|
setTimeout(() => {
|
||||||
|
this.nextBlockTxListSubscription = this.electrsApiService.getBlockTransactions$(this.previousBlockHash).subscribe();
|
||||||
|
}, 100);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
this.txsLoadingStatus$ = this.route.paramMap
|
||||||
|
.pipe(
|
||||||
|
switchMap(() => this.stateService.loadingIndicators$),
|
||||||
|
map((indicators) => indicators['blocktxs-' + this.blockHash] !== undefined ? indicators['blocktxs-' + this.blockHash] : 0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pageChange(page: number, target: HTMLElement): void {
|
||||||
|
target.scrollIntoView(); // works for chrome
|
||||||
|
this.router.navigate([], { queryParams: { page: page }, queryParamsHandling: 'merge' });
|
||||||
|
}
|
||||||
|
|
||||||
|
unsubscribeNextBlockSubscriptions(): void {
|
||||||
|
if (this.nextBlockTxListSubscription !== undefined) {
|
||||||
|
this.nextBlockTxListSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.unsubscribeNextBlockSubscriptions();
|
||||||
|
}
|
||||||
|
}
|
@ -325,53 +325,39 @@
|
|||||||
>Details</button>
|
>Details</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div #blockTxTitle id="block-tx-title" class="block-tx-title">
|
@defer (on viewport) {
|
||||||
<h2 class="text-left">
|
<app-block-transactions [paginationMaxSize]="paginationMaxSize" [block$]="block$" [txCount]="block.tx_count" [timestamp]="block.timestamp" [blockHash]="blockHash" [previousBlockHash]="block.previousblockhash" (blockReward)="updateBlockReward($event)"></app-block-transactions>
|
||||||
<ng-container *ngTemplateOutlet="block.tx_count === 1 ? transactionsSingular : transactionsPlural; context: {$implicit: block.tx_count | number}"></ng-container>
|
} @placeholder {
|
||||||
<ng-template #transactionsSingular let-i i18n="shared.transaction-count.singular">{{ i }} transaction</ng-template>
|
<div>
|
||||||
<ng-template #transactionsPlural let-i i18n="shared.transaction-count.plural">{{ i }} transactions</ng-template>
|
<div class="block-tx-title">
|
||||||
</h2>
|
<h2 class="text-left">
|
||||||
|
<ng-container *ngTemplateOutlet="block.tx_count === 1 ? transactionsSingular : transactionsPlural; context: {$implicit: block.tx_count | number}"></ng-container>
|
||||||
|
<ng-template #transactionsSingular let-i i18n="shared.transaction-count.singular">{{ i }} transaction</ng-template>
|
||||||
|
<ng-template #transactionsPlural let-i i18n="shared.transaction-count.plural">{{ i }} transactions</ng-template>
|
||||||
|
</h2>
|
||||||
|
<ngb-pagination class="pagination-container float-right" [disabled]="true" [collectionSize]="block.tx_count" [rotate]="true" [pageSize]="stateService.env.ITEMS_PER_PAGE" [maxSize]="paginationMaxSize" [boundaryLinks]="true" [ellipses]="false"></ngb-pagination>
|
||||||
|
</div>
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
<div class="text-center mb-4" class="tx-skeleton">
|
||||||
|
|
||||||
<ngb-pagination class="pagination-container float-right" [collectionSize]="block.tx_count" [rotate]="true" [pageSize]="itemsPerPage" [(page)]="page" (pageChange)="pageChange(page, blockTxTitle)" [maxSize]="paginationMaxSize" [boundaryLinks]="true" [ellipses]="false"></ngb-pagination>
|
|
||||||
</div>
|
|
||||||
<div class="clearfix"></div>
|
|
||||||
|
|
||||||
<app-transactions-list [transactions]="transactions" [paginated]="true" [blockTime]="block.timestamp"></app-transactions-list>
|
|
||||||
|
|
||||||
<ng-template [ngIf]="transactionsError">
|
|
||||||
<br>
|
|
||||||
<app-http-error [error]="transactionsError">
|
|
||||||
<span i18n="error.general-loading-data">Error loading data.</span>
|
|
||||||
</app-http-error>
|
|
||||||
<br>
|
|
||||||
<br>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<ng-template [ngIf]="isLoadingTransactions && !transactionsError">
|
|
||||||
<div class="text-center mb-4" class="tx-skeleton">
|
|
||||||
|
|
||||||
<ng-container *ngIf="(txsLoadingStatus$ | async) as txsLoadingStatus; else headerLoader">
|
|
||||||
<div class="header-bg box">
|
<div class="header-bg box">
|
||||||
<div class="progress progress-dark" style="margin: 4px; height: 14px;">
|
<span class="skeleton-loader"></span>
|
||||||
<div class="progress-bar progress-light" role="progressbar" [ngStyle]="{'width': txsLoadingStatus + '%' }"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
<div class="header-bg box">
|
||||||
|
<div class="row">
|
||||||
<div class="header-bg box">
|
<div class="col-sm">
|
||||||
<div class="row">
|
<span class="skeleton-loader"></span>
|
||||||
<div class="col-sm">
|
</div>
|
||||||
<span class="skeleton-loader"></span>
|
<div class="col-sm">
|
||||||
<span class="skeleton-loader"></span>
|
<span class="skeleton-loader"></span>
|
||||||
</div>
|
<span class="skeleton-loader"></span>
|
||||||
<div class="col-sm">
|
<span class="skeleton-loader"></span>
|
||||||
<span class="skeleton-loader"></span>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
}
|
||||||
<ngb-pagination class="pagination-container float-right" [collectionSize]="block.tx_count" [rotate]="true" [pageSize]="itemsPerPage" [(page)]="page" (pageChange)="pageChange(page, blockTxTitle)" [maxSize]="paginationMaxSize" [boundaryLinks]="true" [ellipses]="false"></ngb-pagination>
|
|
||||||
|
|
||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
<br>
|
<br>
|
||||||
@ -382,12 +368,6 @@
|
|||||||
</app-http-error>
|
</app-http-error>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #headerLoader>
|
|
||||||
<div class="header-bg box">
|
|
||||||
<span class="skeleton-loader"></span>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-template #emptyBlockInfo>
|
<ng-template #emptyBlockInfo>
|
||||||
|
@ -21,25 +21,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.qr-wrapper {
|
|
||||||
background-color: var(--fg);
|
|
||||||
padding: 10px;
|
|
||||||
padding-bottom: 5px;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qrcode-col {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qrcode-col > div {
|
|
||||||
margin: 20px auto 5px;
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
text-align: center;
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.fiat {
|
.fiat {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
@ -100,19 +81,7 @@ h1 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.address-link {
|
.row {
|
||||||
line-height: 26px;
|
|
||||||
margin-left: 0px;
|
|
||||||
top: 14px;
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
line-height: 38px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.row{
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
@ -140,28 +109,6 @@ h1 {
|
|||||||
margin-right: .5em;
|
margin-right: .5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.block-tx-title {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
flex-direction: column;
|
|
||||||
margin-top: -15px;
|
|
||||||
position: relative;
|
|
||||||
@media (min-width: 550px) {
|
|
||||||
margin-top: 1rem;
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
h2 {
|
|
||||||
line-height: 1;
|
|
||||||
margin: 0;
|
|
||||||
position: relative;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
@media (min-width: 550px) {
|
|
||||||
padding-bottom: 0px;
|
|
||||||
align-self: end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.grow {
|
.grow {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
@ -204,22 +151,6 @@ h1 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tx-skeleton {
|
|
||||||
margin-top: 10px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
.header-bg {
|
|
||||||
&:first-child {
|
|
||||||
padding: 10px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
&:nth-child(2) {
|
|
||||||
.row {
|
|
||||||
height: 107px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.chart-container{
|
.chart-container{
|
||||||
margin: 20px auto;
|
margin: 20px auto;
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
@ -303,3 +234,41 @@ h1 {
|
|||||||
.graph-col {
|
.graph-col {
|
||||||
flex-grow: 1.11;
|
flex-grow: 1.11;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.block-tx-title {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-top: -15px;
|
||||||
|
position: relative;
|
||||||
|
@media (min-width: 550px) {
|
||||||
|
margin-top: 1rem;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
line-height: 1;
|
||||||
|
margin: 0;
|
||||||
|
position: relative;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
@media (min-width: 550px) {
|
||||||
|
padding-bottom: 0px;
|
||||||
|
align-self: end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tx-skeleton {
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
.header-bg {
|
||||||
|
&:first-child {
|
||||||
|
padding: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
&:nth-child(2) {
|
||||||
|
.row {
|
||||||
|
height: 107px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
import { Component, OnInit, OnDestroy, ViewChildren, QueryList, Inject, PLATFORM_ID, ChangeDetectorRef } from '@angular/core';
|
import { Component, OnInit, OnDestroy, ViewChildren, QueryList, ChangeDetectorRef } from '@angular/core';
|
||||||
import { Location } from '@angular/common';
|
import { Location } from '@angular/common';
|
||||||
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
|
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
|
||||||
import { ElectrsApiService } from '../../services/electrs-api.service';
|
import { ElectrsApiService } from '../../services/electrs-api.service';
|
||||||
import { switchMap, tap, throttleTime, catchError, map, shareReplay, startWith, filter } from 'rxjs/operators';
|
import { switchMap, tap, throttleTime, catchError, map, shareReplay, startWith, filter } from 'rxjs/operators';
|
||||||
import { Transaction, Vout } from '../../interfaces/electrs.interface';
|
|
||||||
import { Observable, of, Subscription, asyncScheduler, EMPTY, combineLatest, forkJoin } from 'rxjs';
|
import { Observable, of, Subscription, asyncScheduler, EMPTY, combineLatest, forkJoin } from 'rxjs';
|
||||||
import { StateService } from '../../services/state.service';
|
import { StateService } from '../../services/state.service';
|
||||||
import { SeoService } from '../../services/seo.service';
|
import { SeoService } from '../../services/seo.service';
|
||||||
import { WebsocketService } from '../../services/websocket.service';
|
import { WebsocketService } from '../../services/websocket.service';
|
||||||
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
|
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
|
||||||
import { AccelerationInfo, BlockAudit, BlockExtended, TransactionStripped } from '../../interfaces/node-api.interface';
|
import { BlockAudit, BlockExtended, TransactionStripped } from '../../interfaces/node-api.interface';
|
||||||
import { ApiService } from '../../services/api.service';
|
import { ApiService } from '../../services/api.service';
|
||||||
import { BlockOverviewGraphComponent } from '../../components/block-overview-graph/block-overview-graph.component';
|
import { BlockOverviewGraphComponent } from '../../components/block-overview-graph/block-overview-graph.component';
|
||||||
import { detectWebGL } from '../../shared/graphs.utils';
|
import { detectWebGL } from '../../shared/graphs.utils';
|
||||||
@ -42,23 +41,17 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
isLoadingBlock = true;
|
isLoadingBlock = true;
|
||||||
latestBlock: BlockExtended;
|
latestBlock: BlockExtended;
|
||||||
latestBlocks: BlockExtended[] = [];
|
latestBlocks: BlockExtended[] = [];
|
||||||
transactions: Transaction[];
|
|
||||||
oobFees: number = 0;
|
oobFees: number = 0;
|
||||||
isLoadingTransactions = true;
|
|
||||||
strippedTransactions: TransactionStripped[];
|
strippedTransactions: TransactionStripped[];
|
||||||
overviewTransitionDirection: string;
|
overviewTransitionDirection: string;
|
||||||
isLoadingOverview = true;
|
isLoadingOverview = true;
|
||||||
error: any;
|
error: any;
|
||||||
blockSubsidy: number;
|
blockSubsidy: number;
|
||||||
fees: number;
|
fees: number;
|
||||||
paginationMaxSize: number;
|
block$: Observable<any>;
|
||||||
page = 1;
|
|
||||||
itemsPerPage: number;
|
|
||||||
txsLoadingStatus$: Observable<number>;
|
|
||||||
showDetails = false;
|
showDetails = false;
|
||||||
showPreviousBlocklink = true;
|
showPreviousBlocklink = true;
|
||||||
showNextBlocklink = true;
|
showNextBlocklink = true;
|
||||||
transactionsError: any = null;
|
|
||||||
overviewError: any = null;
|
overviewError: any = null;
|
||||||
webGlEnabled = true;
|
webGlEnabled = true;
|
||||||
auditParamEnabled: boolean = false;
|
auditParamEnabled: boolean = false;
|
||||||
@ -69,10 +62,10 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
isMobile = window.innerWidth <= 767.98;
|
isMobile = window.innerWidth <= 767.98;
|
||||||
hoverTx: string;
|
hoverTx: string;
|
||||||
numMissing: number = 0;
|
numMissing: number = 0;
|
||||||
|
paginationMaxSize = window.matchMedia('(max-width: 670px)').matches ? 3 : 5;
|
||||||
numUnexpected: number = 0;
|
numUnexpected: number = 0;
|
||||||
mode: 'projected' | 'actual' = 'projected';
|
mode: 'projected' | 'actual' = 'projected';
|
||||||
|
|
||||||
transactionSubscription: Subscription;
|
|
||||||
overviewSubscription: Subscription;
|
overviewSubscription: Subscription;
|
||||||
auditSubscription: Subscription;
|
auditSubscription: Subscription;
|
||||||
keyNavigationSubscription: Subscription;
|
keyNavigationSubscription: Subscription;
|
||||||
@ -82,7 +75,6 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
queryParamsSubscription: Subscription;
|
queryParamsSubscription: Subscription;
|
||||||
nextBlockSubscription: Subscription = undefined;
|
nextBlockSubscription: Subscription = undefined;
|
||||||
nextBlockSummarySubscription: Subscription = undefined;
|
nextBlockSummarySubscription: Subscription = undefined;
|
||||||
nextBlockTxListSubscription: Subscription = undefined;
|
|
||||||
timeLtrSubscription: Subscription;
|
timeLtrSubscription: Subscription;
|
||||||
timeLtr: boolean;
|
timeLtr: boolean;
|
||||||
childChangeSubscription: Subscription;
|
childChangeSubscription: Subscription;
|
||||||
@ -109,16 +101,13 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
private cacheService: CacheService,
|
private cacheService: CacheService,
|
||||||
private servicesApiService: ServicesApiServices,
|
private servicesApiService: ServicesApiServices,
|
||||||
private cd: ChangeDetectorRef,
|
private cd: ChangeDetectorRef,
|
||||||
@Inject(PLATFORM_ID) private platformId: Object,
|
|
||||||
) {
|
) {
|
||||||
this.webGlEnabled = this.stateService.isBrowser && detectWebGL();
|
this.webGlEnabled = this.stateService.isBrowser && detectWebGL();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit(): void {
|
||||||
this.websocketService.want(['blocks', 'mempool-blocks']);
|
this.websocketService.want(['blocks', 'mempool-blocks']);
|
||||||
this.paginationMaxSize = window.matchMedia('(max-width: 670px)').matches ? 3 : 5;
|
|
||||||
this.network = this.stateService.network;
|
this.network = this.stateService.network;
|
||||||
this.itemsPerPage = this.stateService.env.ITEMS_PER_PAGE;
|
|
||||||
|
|
||||||
this.timeLtrSubscription = this.stateService.timeLtr.subscribe((ltr) => {
|
this.timeLtrSubscription = this.stateService.timeLtr.subscribe((ltr) => {
|
||||||
this.timeLtr = !!ltr;
|
this.timeLtr = !!ltr;
|
||||||
@ -139,12 +128,6 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.txsLoadingStatus$ = this.route.paramMap
|
|
||||||
.pipe(
|
|
||||||
switchMap(() => this.stateService.loadingIndicators$),
|
|
||||||
map((indicators) => indicators['blocktxs-' + this.blockHash] !== undefined ? indicators['blocktxs-' + this.blockHash] : 0)
|
|
||||||
);
|
|
||||||
|
|
||||||
this.cacheBlocksSubscription = this.cacheService.loadedBlocks$.subscribe((block) => {
|
this.cacheBlocksSubscription = this.cacheService.loadedBlocks$.subscribe((block) => {
|
||||||
this.loadedCacheBlock(block);
|
this.loadedCacheBlock(block);
|
||||||
});
|
});
|
||||||
@ -172,11 +155,11 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const block$ = this.route.paramMap.pipe(
|
this.block$ = this.route.paramMap.pipe(
|
||||||
switchMap((params: ParamMap) => {
|
switchMap((params: ParamMap) => {
|
||||||
const blockHash: string = params.get('id') || '';
|
const blockHash: string = params.get('id') || '';
|
||||||
this.block = undefined;
|
this.block = undefined;
|
||||||
this.page = 1;
|
// this.page = 1;
|
||||||
this.error = undefined;
|
this.error = undefined;
|
||||||
this.fees = undefined;
|
this.fees = undefined;
|
||||||
this.oobFees = 0;
|
this.oobFees = 0;
|
||||||
@ -259,7 +242,6 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
this.unsubscribeNextBlockSubscriptions();
|
this.unsubscribeNextBlockSubscriptions();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.nextBlockSubscription = this.apiService.getBlock$(block.previousblockhash).subscribe();
|
this.nextBlockSubscription = this.apiService.getBlock$(block.previousblockhash).subscribe();
|
||||||
this.nextBlockTxListSubscription = this.electrsApiService.getBlockTransactions$(block.previousblockhash).subscribe();
|
|
||||||
if (this.auditSupported) {
|
if (this.auditSupported) {
|
||||||
this.apiService.getBlockAudit$(block.previousblockhash);
|
this.apiService.getBlockAudit$(block.previousblockhash);
|
||||||
}
|
}
|
||||||
@ -288,9 +270,6 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
this.fees = block.extras.reward / 100000000 - this.blockSubsidy;
|
this.fees = block.extras.reward / 100000000 - this.blockSubsidy;
|
||||||
}
|
}
|
||||||
this.stateService.markBlock$.next({ blockHeight: this.blockHeight });
|
this.stateService.markBlock$.next({ blockHeight: this.blockHeight });
|
||||||
this.isLoadingTransactions = true;
|
|
||||||
this.transactions = null;
|
|
||||||
this.transactionsError = null;
|
|
||||||
this.isLoadingOverview = true;
|
this.isLoadingOverview = true;
|
||||||
this.overviewError = null;
|
this.overviewError = null;
|
||||||
|
|
||||||
@ -304,31 +283,8 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
throttleTime(300, asyncScheduler, { leading: true, trailing: true }),
|
throttleTime(300, asyncScheduler, { leading: true, trailing: true }),
|
||||||
shareReplay(1)
|
shareReplay(1)
|
||||||
);
|
);
|
||||||
this.transactionSubscription = combineLatest([block$, this.route.queryParams]).pipe(
|
|
||||||
tap(([_, queryParams]) => this.page = +queryParams['page'] || 1),
|
|
||||||
switchMap(([block, _]) => this.electrsApiService.getBlockTransactions$(block.id, (this.page - 1) * this.itemsPerPage)
|
|
||||||
.pipe(
|
|
||||||
catchError((err) => {
|
|
||||||
this.transactionsError = err;
|
|
||||||
return of([]);
|
|
||||||
}))
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.subscribe((transactions: Transaction[]) => {
|
|
||||||
if (this.fees === undefined && transactions[0]) {
|
|
||||||
this.fees = transactions[0].vout.reduce((acc: number, curr: Vout) => acc + curr.value, 0) / 100000000 - this.blockSubsidy;
|
|
||||||
}
|
|
||||||
this.transactions = transactions;
|
|
||||||
this.isLoadingTransactions = false;
|
|
||||||
this.cd.markForCheck();
|
|
||||||
},
|
|
||||||
(error) => {
|
|
||||||
this.error = error;
|
|
||||||
this.isLoadingBlock = false;
|
|
||||||
this.isLoadingOverview = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.overviewSubscription = block$.pipe(
|
this.overviewSubscription = this.block$.pipe(
|
||||||
switchMap((block) => {
|
switchMap((block) => {
|
||||||
return forkJoin([
|
return forkJoin([
|
||||||
this.apiService.getStrippedBlockTransactions$(block.id)
|
this.apiService.getStrippedBlockTransactions$(block.id)
|
||||||
@ -498,14 +454,14 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
this.cd.markForCheck();
|
this.cd.markForCheck();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.oobSubscription = block$.pipe(
|
this.oobSubscription = this.block$.pipe(
|
||||||
filter(() => this.stateService.env.PUBLIC_ACCELERATIONS === true && this.stateService.network === ''),
|
filter(() => this.stateService.env.PUBLIC_ACCELERATIONS === true && this.stateService.network === ''),
|
||||||
switchMap((block) => this.apiService.getAccelerationsByHeight$(block.height)
|
switchMap((block) => this.apiService.getAccelerationsByHeight$(block.height)
|
||||||
.pipe(
|
.pipe(
|
||||||
map(accelerations => {
|
map(accelerations => {
|
||||||
return { block, accelerations };
|
return { block, accelerations };
|
||||||
}),
|
}),
|
||||||
catchError((err) => {
|
catchError(() => {
|
||||||
return of({ block, accelerations: [] });
|
return of({ block, accelerations: [] });
|
||||||
}))
|
}))
|
||||||
),
|
),
|
||||||
@ -560,7 +516,7 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
if (this.priceSubscription) {
|
if (this.priceSubscription) {
|
||||||
this.priceSubscription.unsubscribe();
|
this.priceSubscription.unsubscribe();
|
||||||
}
|
}
|
||||||
this.priceSubscription = combineLatest([this.stateService.fiatCurrency$, block$]).pipe(
|
this.priceSubscription = combineLatest([this.stateService.fiatCurrency$, this.block$]).pipe(
|
||||||
switchMap(([currency, block]) => {
|
switchMap(([currency, block]) => {
|
||||||
return this.priceService.getBlockPrice$(block.timestamp, true, currency).pipe(
|
return this.priceService.getBlockPrice$(block.timestamp, true, currency).pipe(
|
||||||
tap((price) => {
|
tap((price) => {
|
||||||
@ -577,9 +533,8 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy(): void {
|
||||||
this.stateService.markBlock$.next({});
|
this.stateService.markBlock$.next({});
|
||||||
this.transactionSubscription?.unsubscribe();
|
|
||||||
this.overviewSubscription?.unsubscribe();
|
this.overviewSubscription?.unsubscribe();
|
||||||
this.auditSubscription?.unsubscribe();
|
this.auditSubscription?.unsubscribe();
|
||||||
this.keyNavigationSubscription?.unsubscribe();
|
this.keyNavigationSubscription?.unsubscribe();
|
||||||
@ -595,34 +550,22 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
this.oobSubscription?.unsubscribe();
|
this.oobSubscription?.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
unsubscribeNextBlockSubscriptions() {
|
unsubscribeNextBlockSubscriptions(): void {
|
||||||
if (this.nextBlockSubscription !== undefined) {
|
if (this.nextBlockSubscription !== undefined) {
|
||||||
this.nextBlockSubscription.unsubscribe();
|
this.nextBlockSubscription.unsubscribe();
|
||||||
}
|
}
|
||||||
if (this.nextBlockSummarySubscription !== undefined) {
|
if (this.nextBlockSummarySubscription !== undefined) {
|
||||||
this.nextBlockSummarySubscription.unsubscribe();
|
this.nextBlockSummarySubscription.unsubscribe();
|
||||||
}
|
}
|
||||||
if (this.nextBlockTxListSubscription !== undefined) {
|
|
||||||
this.nextBlockTxListSubscription.unsubscribe();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO - Refactor this.fees/this.reward for liquid because it is not
|
// TODO - Refactor this.fees/this.reward for liquid because it is not
|
||||||
// used anymore on Bitcoin networks (we use block.extras directly)
|
// used anymore on Bitcoin networks (we use block.extras directly)
|
||||||
setBlockSubsidy() {
|
setBlockSubsidy(): void {
|
||||||
this.blockSubsidy = 0;
|
this.blockSubsidy = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
pageChange(page: number, target: HTMLElement) {
|
toggleShowDetails(): void {
|
||||||
const start = (page - 1) * this.itemsPerPage;
|
|
||||||
this.isLoadingTransactions = true;
|
|
||||||
this.transactions = null;
|
|
||||||
this.transactionsError = null;
|
|
||||||
target.scrollIntoView(); // works for chrome
|
|
||||||
this.router.navigate([], { queryParams: { page: page }, queryParamsHandling: 'merge' });
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleShowDetails() {
|
|
||||||
if (this.showDetails) {
|
if (this.showDetails) {
|
||||||
this.showDetails = false;
|
this.showDetails = false;
|
||||||
this.router.navigate([], {
|
this.router.navigate([], {
|
||||||
@ -654,7 +597,7 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
return this.block && this.block.height > 681393 && (new Date().getTime() / 1000) < 1628640000;
|
return this.block && this.block.height > 681393 && (new Date().getTime() / 1000) < 1628640000;
|
||||||
}
|
}
|
||||||
|
|
||||||
navigateToPreviousBlock() {
|
navigateToPreviousBlock(): void {
|
||||||
if (!this.block) {
|
if (!this.block) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -663,13 +606,13 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
block ? block.id : this.block.previousblockhash], { state: { data: { block, blockHeight: this.nextBlockHeight - 2 } } });
|
block ? block.id : this.block.previousblockhash], { state: { data: { block, blockHeight: this.nextBlockHeight - 2 } } });
|
||||||
}
|
}
|
||||||
|
|
||||||
navigateToNextBlock() {
|
navigateToNextBlock(): void {
|
||||||
const block = this.latestBlocks.find((b) => b.height === this.nextBlockHeight);
|
const block = this.latestBlocks.find((b) => b.height === this.nextBlockHeight);
|
||||||
this.router.navigate([this.relativeUrlPipe.transform('/block/'),
|
this.router.navigate([this.relativeUrlPipe.transform('/block/'),
|
||||||
block ? block.id : this.nextBlockHeight], { state: { data: { block, blockHeight: this.nextBlockHeight } } });
|
block ? block.id : this.nextBlockHeight], { state: { data: { block, blockHeight: this.nextBlockHeight } } });
|
||||||
}
|
}
|
||||||
|
|
||||||
setNextAndPreviousBlockLink(){
|
setNextAndPreviousBlockLink(): void {
|
||||||
if (this.latestBlock) {
|
if (this.latestBlock) {
|
||||||
if (!this.blockHeight){
|
if (!this.blockHeight){
|
||||||
this.showPreviousBlocklink = false;
|
this.showPreviousBlocklink = false;
|
||||||
@ -701,11 +644,12 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onResize(event: any): void {
|
onResize(event: Event): void {
|
||||||
const isMobile = event.target.innerWidth <= 767.98;
|
const target = event.target as Window;
|
||||||
|
const isMobile = target.innerWidth <= 767.98;
|
||||||
const changed = isMobile !== this.isMobile;
|
const changed = isMobile !== this.isMobile;
|
||||||
this.isMobile = isMobile;
|
this.isMobile = isMobile;
|
||||||
this.paginationMaxSize = event.target.innerWidth < 670 ? 3 : 5;
|
this.paginationMaxSize = target.innerWidth < 670 ? 3 : 5;
|
||||||
|
|
||||||
if (changed) {
|
if (changed) {
|
||||||
this.changeMode(this.mode);
|
this.changeMode(this.mode);
|
||||||
@ -747,11 +691,11 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
this.stateService.hideAudit.next(this.auditModeEnabled);
|
this.stateService.hideAudit.next(this.auditModeEnabled);
|
||||||
|
|
||||||
this.route.queryParams.subscribe(params => {
|
this.route.queryParams.subscribe(params => {
|
||||||
let queryParams = { ...params };
|
const queryParams = { ...params };
|
||||||
delete queryParams['audit'];
|
delete queryParams['audit'];
|
||||||
|
|
||||||
let newUrl = this.router.url.split('?')[0];
|
let newUrl = this.router.url.split('?')[0];
|
||||||
let queryString = new URLSearchParams(queryParams).toString();
|
const queryString = new URLSearchParams(queryParams).toString();
|
||||||
if (queryString) {
|
if (queryString) {
|
||||||
newUrl += '?' + queryString;
|
newUrl += '?' + queryString;
|
||||||
}
|
}
|
||||||
@ -829,4 +773,10 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
this.block.canonical = block.id;
|
this.block.canonical = block.id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateBlockReward(blockReward: number): void {
|
||||||
|
if (this.fees === undefined) {
|
||||||
|
this.fees = blockReward;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -2,6 +2,7 @@ import { NgModule } from '@angular/core';
|
|||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { Routes, RouterModule } from '@angular/router';
|
import { Routes, RouterModule } from '@angular/router';
|
||||||
import { BlockComponent } from './block.component';
|
import { BlockComponent } from './block.component';
|
||||||
|
import { BlockTransactionsComponent } from './block-transactions.component';
|
||||||
import { SharedModule } from '../../shared/shared.module';
|
import { SharedModule } from '../../shared/shared.module';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
@ -32,6 +33,7 @@ export class BlockRoutingModule { }
|
|||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
BlockComponent,
|
BlockComponent,
|
||||||
|
BlockTransactionsComponent,
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class BlockModule { }
|
export class BlockModule { }
|
||||||
|
Loading…
x
Reference in New Issue
Block a user