Refactord blockchain is rendering, block arrow location propagation and keynavigation.

This commit is contained in:
softsimon 2020-03-22 17:44:36 +07:00
parent 69827843c9
commit 78e41fc3d3
No known key found for this signature in database
GPG Key ID: 488D7DCFB5A430D7
15 changed files with 131 additions and 120 deletions

View File

@ -9,6 +9,7 @@ import { AboutComponent } from './components/about/about.component';
import { TelevisionComponent } from './components/television/television.component';
import { StatisticsComponent } from './components/statistics/statistics.component';
import { MempoolBlockComponent } from './components/mempool-block/mempool-block.component';
import { LatestBlocksComponent } from './components/latest-blocks/latest-blocks.component';
const routes: Routes = [
{
@ -18,6 +19,24 @@ const routes: Routes = [
{
path: '',
component: StartComponent,
children: [
{
path: '',
component: LatestBlocksComponent
},
{
path: 'tx/:id',
component: TransactionComponent
},
{
path: 'block/:id',
component: BlockComponent
},
{
path: 'mempool-block/:id',
component: MempoolBlockComponent
},
],
},
{
path: 'graphs',
@ -27,21 +46,6 @@ const routes: Routes = [
path: 'contributors',
component: AboutComponent,
},
{
path: 'tx/:id',
children: [],
component: TransactionComponent
},
{
path: 'block/:id',
children: [],
component: BlockComponent
},
{
path: 'mempool-block/:id',
children: [],
component: MempoolBlockComponent
},
{
path: 'address/:id',
children: [],

View File

@ -1,9 +1,5 @@
<div class="container-xl">
<div style="position: relative;">
<app-blockchain position="top" [markHeight]="blockHeight"></app-blockchain>
</div>
<div class="title-block">
<h1>Block <ng-template [ngIf]="blockHeight"><a [routerLink]="['/block/', blockHash]">{{ blockHeight }}</a></ng-template></h1>
</div>

View File

@ -1,9 +1,9 @@
import { Component, OnInit } from '@angular/core';
import { Component, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute, ParamMap } from '@angular/router';
import { ElectrsApiService } from '../../services/electrs-api.service';
import { switchMap } from 'rxjs/operators';
import { switchMap, tap, debounceTime } from 'rxjs/operators';
import { Block, Transaction, Vout } from '../../interfaces/electrs.interface';
import { of } from 'rxjs';
import { of, Observable } from 'rxjs';
import { StateService } from '../../services/state.service';
import { WebsocketService } from 'src/app/services/websocket.service';
@ -12,7 +12,7 @@ import { WebsocketService } from 'src/app/services/websocket.service';
templateUrl: './block.component.html',
styleUrls: ['./block.component.scss']
})
export class BlockComponent implements OnInit {
export class BlockComponent implements OnInit, OnDestroy {
block: Block;
blockHeight: number;
blockHash: string;
@ -34,7 +34,8 @@ export class BlockComponent implements OnInit {
ngOnInit() {
this.websocketService.want(['blocks', 'stats', 'mempool-blocks']);
this.route.paramMap.pipe(
this.route.paramMap
.pipe(
switchMap((params: ParamMap) => {
const blockHash: string = params.get('id') || '';
this.error = undefined;
@ -54,17 +55,28 @@ export class BlockComponent implements OnInit {
this.isLoadingBlock = true;
return this.electrsApiService.getBlock$(blockHash);
}
})
}),
tap((block: Block) => {
this.block = block;
this.blockHeight = block.height;
this.isLoadingBlock = false;
this.setBlockSubsidy();
if (block.reward) {
this.fees = block.reward / 100000000 - this.blockSubsidy;
}
this.stateService.markBlock$.next({ blockHeight: this.blockHeight });
this.isLoadingTransactions = true;
this.transactions = null;
}),
debounceTime(250),
switchMap((block) => this.electrsApiService.getBlockTransactions$(block.id))
)
.subscribe((block: Block) => {
this.block = block;
this.blockHeight = block.height;
this.isLoadingBlock = false;
this.setBlockSubsidy();
if (block.reward) {
this.fees = block.reward / 100000000 - this.blockSubsidy;
.subscribe((transactions: Transaction[]) => {
if (this.fees === undefined) {
this.fees = transactions[0].vout.reduce((acc: number, curr: Vout) => acc + curr.value, 0) / 100000000 - this.blockSubsidy;
}
this.getBlockTransactions(block.id);
this.transactions = transactions;
this.isLoadingTransactions = false;
},
(error) => {
this.error = error;
@ -75,6 +87,10 @@ export class BlockComponent implements OnInit {
.subscribe((block) => this.latestBlock = block);
}
ngOnDestroy() {
this.stateService.markBlock$.next({});
}
setBlockSubsidy() {
this.blockSubsidy = 50;
let halvenings = Math.floor(this.block.height / 210000);
@ -84,19 +100,6 @@ export class BlockComponent implements OnInit {
}
}
getBlockTransactions(hash: string) {
this.isLoadingTransactions = true;
this.transactions = null;
this.electrsApiService.getBlockTransactions$(hash)
.subscribe((transactions: any) => {
if (this.fees === undefined) {
this.fees = transactions[0].vout.reduce((acc: number, curr: Vout) => acc + curr.value, 0) / 100000000 - this.blockSubsidy;
}
this.transactions = transactions;
this.isLoadingTransactions = false;
});
}
loadMore() {
if (this.isLoadingTransactions || !this.transactions.length || this.transactions.length === this.block.tx_count) {
return;

View File

@ -1,4 +1,4 @@
import { Component, OnInit, OnDestroy, Input, OnChanges, HostListener } from '@angular/core';
import { Component, OnInit, OnDestroy, HostListener } from '@angular/core';
import { Subscription } from 'rxjs';
import { Block } from 'src/app/interfaces/electrs.interface';
import { StateService } from 'src/app/services/state.service';
@ -9,10 +9,9 @@ import { Router } from '@angular/router';
templateUrl: './blockchain-blocks.component.html',
styleUrls: ['./blockchain-blocks.component.scss']
})
export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
@Input() markHeight = 0;
export class BlockchainBlocksComponent implements OnInit, OnDestroy {
blocks: Block[] = [];
markHeight: number;
blocksSubscription: Subscription;
interval: any;
@ -37,10 +36,15 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
this.moveArrowToPosition(true);
});
}
ngOnChanges() {
this.moveArrowToPosition(false);
this.stateService.markBlock$
.subscribe((state) => {
this.markHeight = undefined;
if (state.blockHeight) {
this.markHeight = state.blockHeight;
}
this.moveArrowToPosition(false);
});
}
ngOnDestroy() {

View File

@ -1,7 +1,7 @@
<div class="text-center" class="blockchain-wrapper">
<div class="position-container">
<app-mempool-blocks [markIndex]="markMempoolBlockIndex" [txFeePerVSize]="markHeight ? 0 : txFeePerVSize"></app-mempool-blocks>
<app-blockchain-blocks [markHeight]="markHeight"></app-blockchain-blocks>
<app-mempool-blocks></app-mempool-blocks>
<app-blockchain-blocks></app-blockchain-blocks>
<div id="divider" *ngIf="!isLoading; else loadingTmpl"></div>

View File

@ -1,8 +1,7 @@
import { Component, OnInit, OnDestroy, Input } from '@angular/core';
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';
import { take } from 'rxjs/operators';
import { StateService } from 'src/app/services/state.service';
import { Router } from '@angular/router';
@Component({
selector: 'app-blockchain',
@ -10,12 +9,6 @@ import { Router } from '@angular/router';
styleUrls: ['./blockchain.component.scss']
})
export class BlockchainComponent implements OnInit, OnDestroy {
@Input() position: 'middle' | 'top' = 'middle';
@Input() markHeight: number;
@Input() txFeePerVSize: number;
@Input() markMempoolBlockIndex = -1;
txTrackingSubscription: Subscription;
blocksSubscription: Subscription;
txTrackingLoading = false;
@ -24,7 +17,6 @@ export class BlockchainComponent implements OnInit, OnDestroy {
constructor(
private stateService: StateService,
private router: Router,
) {}
ngOnInit() {

View File

@ -1,3 +1,7 @@
<div class="container-xl">
<hr>
<table class="table table-borderless" [alwaysCallback]="true" [fromRoot]="true" [infiniteScrollContainer]="'body'" infiniteScroll [infiniteScrollDistance]="1.5" [infiniteScrollUpDistance]="1.5" [infiniteScrollThrottle]="50" (scrolled)="loadMore()">
<thead>
<th style="width: 15%;">Height</th>
@ -30,3 +34,5 @@
</ng-template>
</tbody>
</table>
</div>

View File

@ -1,9 +1,5 @@
<div class="container-xl" *ngIf="mempoolBlock">
<div style="position: relative;">
<app-blockchain position="top" [markMempoolBlockIndex]="mempoolBlockIndex"></app-blockchain>
</div>
<div class="title-block">
<h1>Mempool block</h1>
</div>

View File

@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core';
import { Component, OnInit, OnDestroy } from '@angular/core';
import { StateService } from 'src/app/services/state.service';
import { ActivatedRoute, ParamMap } from '@angular/router';
import { switchMap, map } from 'rxjs/operators';
@ -10,7 +10,7 @@ import { WebsocketService } from 'src/app/services/websocket.service';
templateUrl: './mempool-block.component.html',
styleUrls: ['./mempool-block.component.scss']
})
export class MempoolBlockComponent implements OnInit {
export class MempoolBlockComponent implements OnInit, OnDestroy {
mempoolBlockIndex: number;
mempoolBlock: MempoolBlock;
@ -26,6 +26,7 @@ export class MempoolBlockComponent implements OnInit {
this.route.paramMap.pipe(
switchMap((params: ParamMap) => {
this.mempoolBlockIndex = parseInt(params.get('id'), 10) || 0;
this.stateService.markBlock$.next({ mempoolBlockIndex: this.mempoolBlockIndex });
return this.stateService.mempoolBlocks$
.pipe(
map((mempoolBlocks) => mempoolBlocks[this.mempoolBlockIndex])
@ -37,4 +38,8 @@ export class MempoolBlockComponent implements OnInit {
});
}
ngOnDestroy(): void {
this.stateService.markBlock$.next({});
}
}

View File

@ -1,4 +1,4 @@
import { Component, OnInit, OnDestroy, Input, OnChanges, HostListener } from '@angular/core';
import { Component, OnInit, OnDestroy, HostListener } from '@angular/core';
import { Subscription } from 'rxjs';
import { MempoolBlock } from 'src/app/interfaces/websocket.interface';
import { StateService } from 'src/app/services/state.service';
@ -9,7 +9,7 @@ import { Router } from '@angular/router';
templateUrl: './mempool-blocks.component.html',
styleUrls: ['./mempool-blocks.component.scss']
})
export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
export class MempoolBlocksComponent implements OnInit, OnDestroy {
mempoolBlocks: MempoolBlock[];
mempoolBlocksFull: MempoolBlock[];
mempoolBlocksSubscription: Subscription;
@ -21,8 +21,8 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
rightPosition = 0;
transition = '1s';
@Input() txFeePerVSize: number;
@Input() markIndex: number;
markIndex: number;
txFeePerVSize: number;
constructor(
private router: Router,
@ -36,11 +36,23 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
this.mempoolBlocks = this.reduceMempoolBlocksToFitScreen(blocks);
this.calculateTransactionPosition();
});
this.stateService.markBlock$
.subscribe((state) => {
this.markIndex = undefined;
this.txFeePerVSize = undefined;
if (state.mempoolBlockIndex !== undefined) {
this.markIndex = state.mempoolBlockIndex;
}
if (state.txFeePerVSize) {
this.txFeePerVSize = state.txFeePerVSize;
}
this.calculateTransactionPosition();
});
}
@HostListener('window:resize', ['$event'])
onResize() {
console.log('onResize');
if (this.mempoolBlocks.length) {
this.mempoolBlocks = this.reduceMempoolBlocksToFitScreen(JSON.parse(JSON.stringify(this.mempoolBlocksFull)));
}
@ -48,29 +60,27 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
@HostListener('document:keydown', ['$event'])
handleKeyboardEvents(event: KeyboardEvent) {
if (this.markIndex === -1) {
return;
}
if (event.key === 'ArrowRight') {
if (this.mempoolBlocks[this.markIndex - 1]) {
this.router.navigate(['/mempool-block/', this.markIndex - 1]);
} else {
this.stateService.blocks$
.subscribe((block) => {
if (this.stateService.latestBlockHeight === block.height) {
this.router.navigate(['/block/', block.id], { state: { data: { block } }});
}
});
setTimeout(() => {
if (this.markIndex === undefined) {
return;
}
} else if (event.key === 'ArrowLeft') {
if (this.mempoolBlocks[this.markIndex + 1]) {
this.router.navigate(['/mempool-block/', this.markIndex + 1]);
if (event.key === 'ArrowRight') {
if (this.mempoolBlocks[this.markIndex - 1]) {
this.router.navigate(['/mempool-block/', this.markIndex - 1]);
} else {
this.stateService.blocks$
.subscribe((block) => {
if (this.stateService.latestBlockHeight === block.height) {
this.router.navigate(['/block/', block.id], { state: { data: { block } }});
}
});
}
} else if (event.key === 'ArrowLeft') {
if (this.mempoolBlocks[this.markIndex + 1]) {
this.router.navigate(['/mempool-block/', this.markIndex + 1]);
}
}
}
}
ngOnChanges() {
this.calculateTransactionPosition();
});
}
ngOnDestroy() {

View File

@ -1,9 +1,5 @@
<div class="container-xl">
<div style="position: relative;">
<app-blockchain></app-blockchain>
</div>
<div style="position: relative;">
<app-blockchain></app-blockchain>
</div>
<hr>
<app-latest-blocks></app-latest-blocks>
</div>
<router-outlet></router-outlet>

View File

@ -1,11 +1,5 @@
<div class="container-xl">
<div style="position: relative;">
<app-blockchain position="top" [txFeePerVSize]="tx?.fee / (tx?.weight / 4)" [markHeight]="tx?.status?.block_height"></app-blockchain>
</div>
<div class="transaction-content">
<div class="title-block">
<h1 class="float-md-left">Transaction</h1>
@ -181,6 +175,5 @@
</ng-template>
</div>
</div>
<br>

View File

@ -65,6 +65,11 @@ export class TransactionComponent implements OnInit, OnDestroy {
} else {
this.findBlockAndSetFeeRating();
}
if (this.tx.status.confirmed) {
this.stateService.markBlock$.next({ blockHeight: tx.status.block_height });
} else {
this.stateService.markBlock$.next({ txFeePerVSize: tx.fee / (tx.weight / 4) });
}
},
(error) => {
this.error = error;
@ -82,6 +87,7 @@ export class TransactionComponent implements OnInit, OnDestroy {
block_hash: block.id,
block_time: block.timestamp,
};
this.stateService.markBlock$.next({ blockHeight: block.height });
this.audioService.playSound('magic');
this.findBlockAndSetFeeRating();
});
@ -121,5 +127,6 @@ export class TransactionComponent implements OnInit, OnDestroy {
ngOnDestroy() {
this.websocketService.startTrackTransaction('stop');
this.stateService.markBlock$.next({});
}
}

View File

@ -1,8 +1,3 @@
export interface BlockTransaction {
f: number;
}
export interface OptimizedMempoolStats {
id: number;
added: string;
@ -13,7 +8,3 @@ export interface OptimizedMempoolStats {
mempool_byte_weight: number;
vsizes: number[] | string[];
}
interface FeeData {
vsize: { [ fee: string ]: number };
}

View File

@ -4,6 +4,12 @@ import { Block, Transaction } from '../interfaces/electrs.interface';
import { MempoolBlock, MemPoolState } from '../interfaces/websocket.interface';
import { OptimizedMempoolStats } from '../interfaces/node-api.interface';
interface MarkBlockState {
blockHeight?: number;
mempoolBlockIndex?: number;
txFeePerVSize?: number;
}
@Injectable({
providedIn: 'root'
})
@ -21,4 +27,6 @@ export class StateService {
viewFiat$ = new BehaviorSubject<boolean>(false);
connectionState$ = new BehaviorSubject<0 | 1 | 2>(2);
markBlock$ = new Subject<MarkBlockState>();
}