From f3cfa038d3d6a144d0e36668b37fdb979f4781b1 Mon Sep 17 00:00:00 2001 From: Simon Lindh Date: Wed, 19 Feb 2020 23:50:23 +0700 Subject: [PATCH] Transaction tracking revamped. Blockchain block arrow. --- backend/src/index.ts | 12 +- .../components/address/address.component.scss | 1 + .../app/components/block/block.component.html | 240 +++++++++--------- .../app/components/block/block.component.scss | 11 + .../blockchain-blocks.component.html | 1 + .../blockchain-blocks.component.scss | 12 + .../blockchain-blocks.component.ts | 28 +- .../blockchain/blockchain.component.html | 4 +- .../blockchain/blockchain.component.ts | 1 + .../search-form/search-form.component.ts | 8 +- .../transaction/transaction.component.html | 19 +- .../transaction/transaction.component.scss | 12 + .../transaction/transaction.component.ts | 10 +- .../src/app/interfaces/websocket.interface.ts | 3 +- .../src/app/services/websocket.service.ts | 15 +- 15 files changed, 232 insertions(+), 145 deletions(-) diff --git a/backend/src/index.ts b/backend/src/index.ts index 74d348ecf..439506d2b 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -90,8 +90,12 @@ class Server { client['want-stats'] = parsedMessage.data.indexOf('stats') > -1; } - if (parsedMessage && parsedMessage.txId && /^[a-fA-F0-9]{64}$/.test(parsedMessage.txId)) { - client['txId'] = parsedMessage.txId; + if (parsedMessage && parsedMessage['track-tx']) { + 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') { @@ -139,8 +143,8 @@ class Server { return; } - if (client['txId'] && txIds.indexOf(client['txId']) > -1) { - client['txId'] = null; + if (client['track-tx'] && txIds.indexOf(client['track-tx']) > -1) { + client['track-tx'] = null; client.send(JSON.stringify({ 'block': block, 'txConfirmed': true, diff --git a/frontend/src/app/components/address/address.component.scss b/frontend/src/app/components/address/address.component.scss index a42c44689..5e22a9236 100644 --- a/frontend/src/app/components/address/address.component.scss +++ b/frontend/src/app/components/address/address.component.scss @@ -5,6 +5,7 @@ .qr-wrapper { background-color: #FFF; padding: 10px; + padding-bottom: 5px; display: inline-block; margin-right: 25px; } \ No newline at end of file diff --git a/frontend/src/app/components/block/block.component.html b/frontend/src/app/components/block/block.component.html index 73e8fbd33..de34b56db 100644 --- a/frontend/src/app/components/block/block.component.html +++ b/frontend/src/app/components/block/block.component.html @@ -1,134 +1,136 @@
- + -

Block #{{ blockHeight }}

- - +
+

Block #{{ blockHeight }}

+
-
- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - -
Timestamp{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }} ( ago)
Number of transactions{{ block.tx_count }}
Size{{ block.size | bytes: 2 }}
Weight{{ block.weight | wuBytes: 2 }}
Status
-
- + + +
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Timestamp{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }} ( ago)
Number of transactions{{ block.tx_count }}
Size{{ block.size | bytes: 2 }}
Weight{{ block.weight | wuBytes: 2 }}
Status
+
+
+
-
+
-

{{ transactions?.length || '?' }} of {{ block.tx_count }} transactions

+

{{ transactions?.length || '?' }} of {{ block.tx_count }} transactions

-
+
- + -
- -
-

-
- -
- -
- - - -
- -
-
-
- - - - - - - - - - - - - - - - - - -
-
-
- - - - - - - - - -
-
-
-
- -
- -
+
+


-
- + + +
- -
- Error loading block data. -
- {{ error.error }} -
-
+
-
- -
\ No newline at end of file + + +
+ +
+
+
+ + + + + + + + + + + + + + + + + + +
+
+
+ + + + + + + + + +
+
+
+
+ +
+ +
+
+

+
+
+ + +
+ Error loading block data. +
+ {{ error.error }} +
+
+ +
+ +
\ No newline at end of file diff --git a/frontend/src/app/components/block/block.component.scss b/frontend/src/app/components/block/block.component.scss index e69de29bb..ada1b97d2 100644 --- a/frontend/src/app/components/block/block.component.scss +++ b/frontend/src/app/components/block/block.component.scss @@ -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; +} diff --git a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html index 07f00d68b..94e95c2b1 100644 --- a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html +++ b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html @@ -18,4 +18,5 @@
+
diff --git a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.scss b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.scss index f3e3589c1..7c804bb67 100644 --- a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.scss +++ b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.scss @@ -101,3 +101,15 @@ z-index: 100; 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; +} \ No newline at end of file diff --git a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.ts b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.ts index d53ed5603..89c4f4512 100644 --- a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.ts +++ b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.ts @@ -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 { Block } from 'src/app/interfaces/electrs.interface'; 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', styleUrls: ['./blockchain-blocks.component.scss'] }) -export class BlockchainBlocksComponent implements OnInit, OnDestroy { +export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy { + @Input() markHeight = 0; + blocks: Block[] = []; blocksSubscription: Subscription; interval: any; trigger = 0; + + arrowVisible = false; + arrowLeftPx = 30; + constructor( private stateService: StateService, ) { } @@ -26,16 +32,34 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy { } this.blocks.unshift(block); this.blocks = this.blocks.slice(0, 8); + + this.moveArrowToPosition(); }); this.interval = setInterval(() => this.trigger++, 10 * 1000); } + ngOnChanges() { + this.moveArrowToPosition(); + } + ngOnDestroy() { this.blocksSubscription.unsubscribe(); 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) { return item.height; } diff --git a/frontend/src/app/components/blockchain/blockchain.component.html b/frontend/src/app/components/blockchain/blockchain.component.html index 6bf4e8b5e..22eb19710 100644 --- a/frontend/src/app/components/blockchain/blockchain.component.html +++ b/frontend/src/app/components/blockchain/blockchain.component.html @@ -4,9 +4,9 @@
-
+
- +
diff --git a/frontend/src/app/components/blockchain/blockchain.component.ts b/frontend/src/app/components/blockchain/blockchain.component.ts index d29849272..f47953ff5 100644 --- a/frontend/src/app/components/blockchain/blockchain.component.ts +++ b/frontend/src/app/components/blockchain/blockchain.component.ts @@ -10,6 +10,7 @@ import { StateService } from 'src/app/services/state.service'; }) export class BlockchainComponent implements OnInit, OnDestroy { @Input() position: 'middle' | 'top' = 'middle'; + @Input() markHeight: number; txTrackingSubscription: Subscription; blocksSubscription: Subscription; diff --git a/frontend/src/app/components/search-form/search-form.component.ts b/frontend/src/app/components/search-form/search-form.component.ts index 73c41e14d..cc0d083e5 100644 --- a/frontend/src/app/components/search-form/search-form.component.ts +++ b/frontend/src/app/components/search-form/search-form.component.ts @@ -15,6 +15,8 @@ export class SearchFormComponent implements OnInit { 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})$/; + regexBlockhash = /^[0]{8}[a-fA-F0-9]{56}$/; + regexTransaction = /^[a-fA-F0-9]{64}$/; constructor( private formBuilder: FormBuilder, @@ -32,8 +34,12 @@ export class SearchFormComponent implements OnInit { if (searchText) { if (this.regexAddress.test(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]); + } else { + return; } this.searchForm.setValue({ searchText: '', diff --git a/frontend/src/app/components/transaction/transaction.component.html b/frontend/src/app/components/transaction/transaction.component.html index f5f7518bc..4cbbfb1cc 100644 --- a/frontend/src/app/components/transaction/transaction.component.html +++ b/frontend/src/app/components/transaction/transaction.component.html @@ -1,10 +1,14 @@
- + -
+
+

Transaction

+ {{ txId }} + +
-

Transaction

+
@@ -51,19 +55,14 @@ - - + - - +
Transaction - {{ txId | shortenString }} - - Status
Fees{{ tx.fee | number }} sats ({{ conversions.USD * tx.fee / 100000000 | currency:'USD':'symbol':'1.2-2' }}){{ tx.fee / (tx.weight / 4) | number : '1.2-2' }} sat/vB{{ tx.fee | number }} sats ({{ conversions.USD * tx.fee / 100000000 | currency:'USD':'symbol':'1.2-2' }}) {{ tx.fee / (tx.weight / 4) | number : '1.2-2' }} sat/vB
diff --git a/frontend/src/app/components/transaction/transaction.component.scss b/frontend/src/app/components/transaction/transaction.component.scss index 78fc9a8bb..0c333589d 100644 --- a/frontend/src/app/components/transaction/transaction.component.scss +++ b/frontend/src/app/components/transaction/transaction.component.scss @@ -10,3 +10,15 @@ 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; +} diff --git a/frontend/src/app/components/transaction/transaction.component.ts b/frontend/src/app/components/transaction/transaction.component.ts index 1b3d83326..08b0f2142 100644 --- a/frontend/src/app/components/transaction/transaction.component.ts +++ b/frontend/src/app/components/transaction/transaction.component.ts @@ -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 { ActivatedRoute, ParamMap } from '@angular/router'; import { switchMap } from 'rxjs/operators'; @@ -12,7 +12,7 @@ import { WebsocketService } from '../../services/websocket.service'; templateUrl: './transaction.component.html', styleUrls: ['./transaction.component.scss'] }) -export class TransactionComponent implements OnInit { +export class TransactionComponent implements OnInit, OnDestroy { tx: Transaction; txId: string; isLoadingTx = true; @@ -51,7 +51,7 @@ export class TransactionComponent implements OnInit { window.scrollTo(0, 0); if (!tx.status.confirmed) { - this.websocketService.startTrackTx(tx.txid); + this.websocketService.startTrackTransaction(tx.txid); } }, (error) => { @@ -75,4 +75,8 @@ export class TransactionComponent implements OnInit { }; }); } + + ngOnDestroy() { + this.websocketService.startTrackTransaction('stop'); + } } diff --git a/frontend/src/app/interfaces/websocket.interface.ts b/frontend/src/app/interfaces/websocket.interface.ts index 6d69e1315..852ba6bf9 100644 --- a/frontend/src/app/interfaces/websocket.interface.ts +++ b/frontend/src/app/interfaces/websocket.interface.ts @@ -4,13 +4,14 @@ export interface WebsocketResponse { block?: Block; blocks?: Block[]; conversions?: any; - txId?: string; txConfirmed?: boolean; historicalDate?: string; mempoolInfo?: MempoolInfo; vBytesPerSecond?: number; action?: string; data?: string[]; + 'track-tx'?: string; + 'track-address'?: string; } export interface MempoolBlock { diff --git a/frontend/src/app/services/websocket.service.ts b/frontend/src/app/services/websocket.service.ts index fa4373ddf..47d2405d3 100644 --- a/frontend/src/app/services/websocket.service.ts +++ b/frontend/src/app/services/websocket.service.ts @@ -16,6 +16,7 @@ export class WebsocketService { private goneOffline = false; private lastWant: string[] | null = null; private trackingTxId: string | null = null; + private trackingAddress: string | null = null; constructor( private stateService: StateService, @@ -86,7 +87,10 @@ export class WebsocketService { this.want(this.lastWant); } if (this.trackingTxId) { - this.startTrackTx(this.trackingTxId); + this.startTrackTransaction(this.trackingTxId); + } + if (this.trackingAddress) { + this.startTrackTransaction(this.trackingAddress); } this.stateService.isOffline$.next(false); } @@ -99,11 +103,16 @@ export class WebsocketService { }); } - startTrackTx(txId: string) { - this.websocketSubject.next({ txId }); + startTrackTransaction(txId: string) { + this.websocketSubject.next({ 'track-tx': txId }); this.trackingTxId = txId; } + startTrackAddress(address: string) { + this.websocketSubject.next({ 'track-address': address }); + this.trackingAddress = address; + } + fetchStatistics(historicalDate: string) { this.websocketSubject.next({ historicalDate }); }