Transaction tracking revamped.
Blockchain block arrow.
This commit is contained in:
parent
34645908e9
commit
f3cfa038d3
@ -90,8 +90,12 @@ class Server {
|
|||||||
client['want-stats'] = parsedMessage.data.indexOf('stats') > -1;
|
client['want-stats'] = parsedMessage.data.indexOf('stats') > -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parsedMessage && parsedMessage.txId && /^[a-fA-F0-9]{64}$/.test(parsedMessage.txId)) {
|
if (parsedMessage && parsedMessage['track-tx']) {
|
||||||
client['txId'] = parsedMessage.txId;
|
if (/^[a-fA-F0-9]{64}$/.test(parsedMessage['track-tx'])) {
|
||||||
|
client['track-tx'] = parsedMessage['track-tx'];
|
||||||
|
} else {
|
||||||
|
client['track-tx'] = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parsedMessage.action === 'init') {
|
if (parsedMessage.action === 'init') {
|
||||||
@ -139,8 +143,8 @@ class Server {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (client['txId'] && txIds.indexOf(client['txId']) > -1) {
|
if (client['track-tx'] && txIds.indexOf(client['track-tx']) > -1) {
|
||||||
client['txId'] = null;
|
client['track-tx'] = null;
|
||||||
client.send(JSON.stringify({
|
client.send(JSON.stringify({
|
||||||
'block': block,
|
'block': block,
|
||||||
'txConfirmed': true,
|
'txConfirmed': true,
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
.qr-wrapper {
|
.qr-wrapper {
|
||||||
background-color: #FFF;
|
background-color: #FFF;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
padding-bottom: 5px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-right: 25px;
|
margin-right: 25px;
|
||||||
}
|
}
|
@ -1,8 +1,10 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
|
|
||||||
<app-blockchain position="top"></app-blockchain>
|
<app-blockchain position="top" [markHeight]="blockHeight"></app-blockchain>
|
||||||
|
|
||||||
|
<div class="title-block">
|
||||||
<h1>Block <ng-template [ngIf]="blockHeight"><a [routerLink]="['/block/', blockHash]">#{{ blockHeight }}</a></ng-template></h1>
|
<h1>Block <ng-template [ngIf]="blockHeight"><a [routerLink]="['/block/', blockHash]">#{{ blockHeight }}</a></ng-template></h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ng-template [ngIf]="!isLoadingBlock && !error">
|
<ng-template [ngIf]="!isLoadingBlock && !error">
|
||||||
|
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
.title-block {
|
||||||
|
color: #FFF;
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-top: 13px;
|
||||||
|
padding-bottom: 3px;
|
||||||
|
border-top: 5px solid #FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-block > h1 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
@ -18,4 +18,5 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div [hidden]="!arrowVisible" id="arrow-up" [ngStyle]="{'left': arrowLeftPx + 'px' }"></div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -101,3 +101,15 @@
|
|||||||
z-index: 100;
|
z-index: 100;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#arrow-up {
|
||||||
|
position: relative;
|
||||||
|
left: 30px;
|
||||||
|
top: 140px;
|
||||||
|
transition: 1s;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-left: 35px solid transparent;
|
||||||
|
border-right: 35px solid transparent;
|
||||||
|
border-bottom: 35px solid #FFF;
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
import { Component, OnInit, OnDestroy, Input, OnChanges } from '@angular/core';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
import { Block } from 'src/app/interfaces/electrs.interface';
|
import { Block } from 'src/app/interfaces/electrs.interface';
|
||||||
import { StateService } from 'src/app/services/state.service';
|
import { StateService } from 'src/app/services/state.service';
|
||||||
@ -8,12 +8,18 @@ import { StateService } from 'src/app/services/state.service';
|
|||||||
templateUrl: './blockchain-blocks.component.html',
|
templateUrl: './blockchain-blocks.component.html',
|
||||||
styleUrls: ['./blockchain-blocks.component.scss']
|
styleUrls: ['./blockchain-blocks.component.scss']
|
||||||
})
|
})
|
||||||
export class BlockchainBlocksComponent implements OnInit, OnDestroy {
|
export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
|
@Input() markHeight = 0;
|
||||||
|
|
||||||
blocks: Block[] = [];
|
blocks: Block[] = [];
|
||||||
blocksSubscription: Subscription;
|
blocksSubscription: Subscription;
|
||||||
interval: any;
|
interval: any;
|
||||||
trigger = 0;
|
trigger = 0;
|
||||||
|
|
||||||
|
|
||||||
|
arrowVisible = false;
|
||||||
|
arrowLeftPx = 30;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private stateService: StateService,
|
private stateService: StateService,
|
||||||
) { }
|
) { }
|
||||||
@ -26,16 +32,34 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
this.blocks.unshift(block);
|
this.blocks.unshift(block);
|
||||||
this.blocks = this.blocks.slice(0, 8);
|
this.blocks = this.blocks.slice(0, 8);
|
||||||
|
|
||||||
|
this.moveArrowToPosition();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.interval = setInterval(() => this.trigger++, 10 * 1000);
|
this.interval = setInterval(() => this.trigger++, 10 * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnChanges() {
|
||||||
|
this.moveArrowToPosition();
|
||||||
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
this.blocksSubscription.unsubscribe();
|
this.blocksSubscription.unsubscribe();
|
||||||
clearInterval(this.interval);
|
clearInterval(this.interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
moveArrowToPosition() {
|
||||||
|
if (!this.markHeight) {
|
||||||
|
this.arrowVisible = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const blockindex = this.blocks.findIndex((b) => b.height === this.markHeight);
|
||||||
|
if (blockindex !== -1) {
|
||||||
|
this.arrowVisible = true;
|
||||||
|
this.arrowLeftPx = blockindex * 150 + 30;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
trackByBlocksFn(index: number, item: Block) {
|
trackByBlocksFn(index: number, item: Block) {
|
||||||
return item.height;
|
return item.height;
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,9 @@
|
|||||||
<div class="spinner-border text-light"></div>
|
<div class="spinner-border text-light"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-center" class="blockchain-wrapper">
|
<div class="text-center" class="blockchain-wrapper">
|
||||||
<div class="position-container" [ngStyle]="{'top': position === 'top' ? '100px' : 'calc(50% - 60px)'}">
|
<div class="position-container" [ngStyle]="{'top': position === 'top' ? '75px' : 'calc(50% - 60px)'}">
|
||||||
<app-mempool-blocks></app-mempool-blocks>
|
<app-mempool-blocks></app-mempool-blocks>
|
||||||
<app-blockchain-blocks></app-blockchain-blocks>
|
<app-blockchain-blocks [markHeight]="markHeight"></app-blockchain-blocks>
|
||||||
|
|
||||||
<div id="divider" *ngIf="!isLoading"></div>
|
<div id="divider" *ngIf="!isLoading"></div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -10,6 +10,7 @@ import { StateService } from 'src/app/services/state.service';
|
|||||||
})
|
})
|
||||||
export class BlockchainComponent implements OnInit, OnDestroy {
|
export class BlockchainComponent implements OnInit, OnDestroy {
|
||||||
@Input() position: 'middle' | 'top' = 'middle';
|
@Input() position: 'middle' | 'top' = 'middle';
|
||||||
|
@Input() markHeight: number;
|
||||||
|
|
||||||
txTrackingSubscription: Subscription;
|
txTrackingSubscription: Subscription;
|
||||||
blocksSubscription: Subscription;
|
blocksSubscription: Subscription;
|
||||||
|
@ -15,6 +15,8 @@ export class SearchFormComponent implements OnInit {
|
|||||||
searchBoxPlaceholderText = 'Transaction, address, block hash...';
|
searchBoxPlaceholderText = 'Transaction, address, block hash...';
|
||||||
|
|
||||||
regexAddress = /^([a-km-zA-HJ-NP-Z1-9]{26,35}|[a-km-zA-HJ-NP-Z1-9]{80}|[a-z]{2,5}1[ac-hj-np-z02-9]{8,87})$/;
|
regexAddress = /^([a-km-zA-HJ-NP-Z1-9]{26,35}|[a-km-zA-HJ-NP-Z1-9]{80}|[a-z]{2,5}1[ac-hj-np-z02-9]{8,87})$/;
|
||||||
|
regexBlockhash = /^[0]{8}[a-fA-F0-9]{56}$/;
|
||||||
|
regexTransaction = /^[a-fA-F0-9]{64}$/;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private formBuilder: FormBuilder,
|
private formBuilder: FormBuilder,
|
||||||
@ -32,8 +34,12 @@ export class SearchFormComponent implements OnInit {
|
|||||||
if (searchText) {
|
if (searchText) {
|
||||||
if (this.regexAddress.test(searchText)) {
|
if (this.regexAddress.test(searchText)) {
|
||||||
this.router.navigate(['/address/', searchText]);
|
this.router.navigate(['/address/', searchText]);
|
||||||
} else {
|
} else if (this.regexBlockhash.test(searchText)) {
|
||||||
|
this.router.navigate(['/block/', searchText]);
|
||||||
|
} else if (this.regexTransaction.test(searchText)) {
|
||||||
this.router.navigate(['/tx/', searchText]);
|
this.router.navigate(['/tx/', searchText]);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
this.searchForm.setValue({
|
this.searchForm.setValue({
|
||||||
searchText: '',
|
searchText: '',
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
|
|
||||||
<app-blockchain position="top"></app-blockchain>
|
<app-blockchain position="top" [markHeight]="tx?.status?.block_height"></app-blockchain>
|
||||||
|
|
||||||
<div class="clearfix"></div>
|
<div class="title-block">
|
||||||
|
<h1 style="float: left;">Transaction</h1>
|
||||||
|
<a [routerLink]="['/tx/', txId]" style="line-height: 55px; margin-left: 10px;">{{ txId }}</a>
|
||||||
|
<app-clipboard [text]="txId"></app-clipboard>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h1>Transaction</h1>
|
<br>
|
||||||
|
|
||||||
<ng-template [ngIf]="!isLoadingTx && !error">
|
<ng-template [ngIf]="!isLoadingTx && !error">
|
||||||
|
|
||||||
@ -51,19 +55,14 @@
|
|||||||
<table class="table table-borderless table-striped">
|
<table class="table table-borderless table-striped">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Transaction</td>
|
<td>Status</td>
|
||||||
<td>
|
|
||||||
<a [routerLink]="['/tx/', txId]">{{ txId | shortenString }}</a>
|
|
||||||
<app-clipboard [text]="txId"></app-clipboard>
|
|
||||||
</td>
|
|
||||||
<td class="adjust-btn-padding">
|
<td class="adjust-btn-padding">
|
||||||
<button type="button" class="btn btn-sm btn-danger">Unconfirmed</button>
|
<button type="button" class="btn btn-sm btn-danger">Unconfirmed</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Fees</td>
|
<td>Fees</td>
|
||||||
<td>{{ tx.fee | number }} sats <span *ngIf="conversions">(<span class="green-color">{{ conversions.USD * tx.fee / 100000000 | currency:'USD':'symbol':'1.2-2' }}</span>)</span></td>
|
<td>{{ tx.fee | number }} sats <span *ngIf="conversions">(<span class="green-color">{{ conversions.USD * tx.fee / 100000000 | currency:'USD':'symbol':'1.2-2' }}</span>)</span> {{ tx.fee / (tx.weight / 4) | number : '1.2-2' }} sat/vB</td>
|
||||||
<td>{{ tx.fee / (tx.weight / 4) | number : '1.2-2' }} sat/vB</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@ -10,3 +10,15 @@
|
|||||||
|
|
||||||
width: 40px;
|
width: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.title-block {
|
||||||
|
color: #FFF;
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-top: 13px;
|
||||||
|
padding-bottom: 3px;
|
||||||
|
border-top: 5px solid #FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-block > h1 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||||
import { ElectrsApiService } from '../../services/electrs-api.service';
|
import { ElectrsApiService } from '../../services/electrs-api.service';
|
||||||
import { ActivatedRoute, ParamMap } from '@angular/router';
|
import { ActivatedRoute, ParamMap } from '@angular/router';
|
||||||
import { switchMap } from 'rxjs/operators';
|
import { switchMap } from 'rxjs/operators';
|
||||||
@ -12,7 +12,7 @@ import { WebsocketService } from '../../services/websocket.service';
|
|||||||
templateUrl: './transaction.component.html',
|
templateUrl: './transaction.component.html',
|
||||||
styleUrls: ['./transaction.component.scss']
|
styleUrls: ['./transaction.component.scss']
|
||||||
})
|
})
|
||||||
export class TransactionComponent implements OnInit {
|
export class TransactionComponent implements OnInit, OnDestroy {
|
||||||
tx: Transaction;
|
tx: Transaction;
|
||||||
txId: string;
|
txId: string;
|
||||||
isLoadingTx = true;
|
isLoadingTx = true;
|
||||||
@ -51,7 +51,7 @@ export class TransactionComponent implements OnInit {
|
|||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
|
|
||||||
if (!tx.status.confirmed) {
|
if (!tx.status.confirmed) {
|
||||||
this.websocketService.startTrackTx(tx.txid);
|
this.websocketService.startTrackTransaction(tx.txid);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
@ -75,4 +75,8 @@ export class TransactionComponent implements OnInit {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.websocketService.startTrackTransaction('stop');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,13 +4,14 @@ export interface WebsocketResponse {
|
|||||||
block?: Block;
|
block?: Block;
|
||||||
blocks?: Block[];
|
blocks?: Block[];
|
||||||
conversions?: any;
|
conversions?: any;
|
||||||
txId?: string;
|
|
||||||
txConfirmed?: boolean;
|
txConfirmed?: boolean;
|
||||||
historicalDate?: string;
|
historicalDate?: string;
|
||||||
mempoolInfo?: MempoolInfo;
|
mempoolInfo?: MempoolInfo;
|
||||||
vBytesPerSecond?: number;
|
vBytesPerSecond?: number;
|
||||||
action?: string;
|
action?: string;
|
||||||
data?: string[];
|
data?: string[];
|
||||||
|
'track-tx'?: string;
|
||||||
|
'track-address'?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MempoolBlock {
|
export interface MempoolBlock {
|
||||||
|
@ -16,6 +16,7 @@ export class WebsocketService {
|
|||||||
private goneOffline = false;
|
private goneOffline = false;
|
||||||
private lastWant: string[] | null = null;
|
private lastWant: string[] | null = null;
|
||||||
private trackingTxId: string | null = null;
|
private trackingTxId: string | null = null;
|
||||||
|
private trackingAddress: string | null = null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private stateService: StateService,
|
private stateService: StateService,
|
||||||
@ -86,7 +87,10 @@ export class WebsocketService {
|
|||||||
this.want(this.lastWant);
|
this.want(this.lastWant);
|
||||||
}
|
}
|
||||||
if (this.trackingTxId) {
|
if (this.trackingTxId) {
|
||||||
this.startTrackTx(this.trackingTxId);
|
this.startTrackTransaction(this.trackingTxId);
|
||||||
|
}
|
||||||
|
if (this.trackingAddress) {
|
||||||
|
this.startTrackTransaction(this.trackingAddress);
|
||||||
}
|
}
|
||||||
this.stateService.isOffline$.next(false);
|
this.stateService.isOffline$.next(false);
|
||||||
}
|
}
|
||||||
@ -99,11 +103,16 @@ export class WebsocketService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
startTrackTx(txId: string) {
|
startTrackTransaction(txId: string) {
|
||||||
this.websocketSubject.next({ txId });
|
this.websocketSubject.next({ 'track-tx': txId });
|
||||||
this.trackingTxId = txId;
|
this.trackingTxId = txId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
startTrackAddress(address: string) {
|
||||||
|
this.websocketSubject.next({ 'track-address': address });
|
||||||
|
this.trackingAddress = address;
|
||||||
|
}
|
||||||
|
|
||||||
fetchStatistics(historicalDate: string) {
|
fetchStatistics(historicalDate: string) {
|
||||||
this.websocketSubject.next({ historicalDate });
|
this.websocketSubject.next({ historicalDate });
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user