From 93c5f0bd841cbca28d54d9496d780b2ee1ecf047 Mon Sep 17 00:00:00 2001 From: softsimon Date: Mon, 23 Mar 2020 04:07:31 +0700 Subject: [PATCH] Adding ETA, confirmed time, and other ui improvements to transaction page. --- frontend/src/app/app.module.ts | 2 + .../app/components/block/block.component.html | 2 +- .../app/components/block/block.component.scss | 5 - .../mempool-block.component.scss | 5 - .../components/timespan/timespan.component.ts | 44 +++++ .../transaction/transaction.component.html | 180 ++++++++++++------ .../transaction/transaction.component.scss | 17 +- .../transaction/transaction.component.ts | 30 ++- .../transactions-list.component.html | 2 +- frontend/src/styles.scss | 4 + 10 files changed, 207 insertions(+), 84 deletions(-) create mode 100644 frontend/src/app/components/timespan/timespan.component.ts diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 60b54361b..6a29d76ef 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -42,6 +42,7 @@ import { AudioService } from './services/audio.service'; import { FiatComponent } from './fiat/fiat.component'; import { MempoolBlockComponent } from './components/mempool-block/mempool-block.component'; import { FeeDistributionGraphComponent } from './components/fee-distribution-graph/fee-distribution-graph.component'; +import { TimespanComponent } from './components/timespan/timespan.component'; @NgModule({ declarations: [ @@ -66,6 +67,7 @@ import { FeeDistributionGraphComponent } from './components/fee-distribution-gra SearchFormComponent, LatestBlocksComponent, TimeSinceComponent, + TimespanComponent, AddressLabelsComponent, MempoolBlocksComponent, QrcodeComponent, diff --git a/frontend/src/app/components/block/block.component.html b/frontend/src/app/components/block/block.component.html index 0b4449bbf..467bd9672 100644 --- a/frontend/src/app/components/block/block.component.html +++ b/frontend/src/app/components/block/block.component.html @@ -18,7 +18,7 @@ {{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }}
- ( ago) + ( ago)
diff --git a/frontend/src/app/components/block/block.component.scss b/frontend/src/app/components/block/block.component.scss index 589490939..859b81409 100644 --- a/frontend/src/app/components/block/block.component.scss +++ b/frontend/src/app/components/block/block.component.scss @@ -1,15 +1,10 @@ .title-block { color: #FFF; - padding-left: 10px; padding-top: 20px; padding-bottom: 3px; border-top: 5px solid #FFF; } -.title-block > h1 { - margin: 0; -} - .mobile-width { width: 130px; } diff --git a/frontend/src/app/components/mempool-block/mempool-block.component.scss b/frontend/src/app/components/mempool-block/mempool-block.component.scss index 4893d9ccc..e9d02f6fc 100644 --- a/frontend/src/app/components/mempool-block/mempool-block.component.scss +++ b/frontend/src/app/components/mempool-block/mempool-block.component.scss @@ -14,12 +14,7 @@ .title-block { color: #FFF; - padding-left: 10px; padding-top: 20px; padding-bottom: 3px; border-top: 5px solid #FFF; -} - -.title-block > h1 { - margin: 0; } \ No newline at end of file diff --git a/frontend/src/app/components/timespan/timespan.component.ts b/frontend/src/app/components/timespan/timespan.component.ts new file mode 100644 index 000000000..6433aa250 --- /dev/null +++ b/frontend/src/app/components/timespan/timespan.component.ts @@ -0,0 +1,44 @@ +import { Component, ChangeDetectionStrategy, Input, OnChanges } from '@angular/core'; + +@Component({ + selector: 'app-timespan', + template: `{{ text }}`, + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class TimespanComponent implements OnChanges { + @Input() time: number; + text: string; + + constructor() { } + + ngOnChanges() { + const seconds = this.time; + if (seconds < 60) { + return '< 1 minute'; + } + const intervals = { + year: 31536000, + month: 2592000, + week: 604800, + day: 86400, + hour: 3600, + minute: 60, + second: 1 + }; + let counter; + for (const i in intervals) { + if (intervals.hasOwnProperty(i)) { + counter = Math.floor(seconds / intervals[i]); + if (counter > 0) { + if (counter === 1) { + this.text = counter + ' ' + i; // singular (1 day ago) + break; + } else { + this.text = counter + ' ' + i + 's'; // plural (2 days ago) + break; + } + } + } + } + } +} diff --git a/frontend/src/app/components/transaction/transaction.component.html b/frontend/src/app/components/transaction/transaction.component.html index 0c0b3912e..687ff3dfc 100644 --- a/frontend/src/app/components/transaction/transaction.component.html +++ b/frontend/src/app/components/transaction/transaction.component.html @@ -17,44 +17,58 @@ +
-
-
- - - - - - - - - - - - - - - - - - -
Included in block - {{ tx.status.block_height }} -
 at {{ tx.status.block_time * 1000 | date:'yyyy-MM-dd HH:mm' }}
-
 ( ago)
-
Fee per vByte - {{ tx.fee / (tx.weight / 4) | number : '1.2-2' }} sats/vB -   - Optimal - Overpaid {{ overpaidTimes }}x - Overpaid {{ overpaidTimes }}x -
Fee{{ tx.fee | number }} sats ()
+
+
+ + + + + + + + + + + + + +
Included in block + {{ tx.status.block_height }} + ( ago) +
ConfirmedAfter
+
+
+ + + + + + + + + + + + + +
Fee{{ tx.fee | number }} sats ()
Fee per vByte + {{ tx.fee / (tx.weight / 4) | number : '1.1-1' }} sats/vB +   + Optimal + Overpaid {{ overpaidTimes }}x + Overpaid {{ overpaidTimes }}x +
+
+ +

@@ -64,31 +78,51 @@
- - - - - - - - - +
+
+
+ + + + + + + + + + + + + - - + + - - - - - - - - - - - -
First seen ago
First seen agoETA + + + + + ~{{ 10 * txInBlockIndex + 10 }} minutes ({{ txInBlockIndex + 1 }} block{{ txInBlockIndex > 0 ? 's' : '' }}) + +
Fees{{ tx.fee | number }} sats ()
Fees per vByte{{ tx.fee / (tx.weight / 4) | number : '1.2-2' }} sats/vB
+ + +
+
+ + + + + + + + + + + +
Fee{{ tx.fee | number }} sats ()
Fee per vByte{{ tx.fee / (tx.weight / 4) | number : '1.1-1' }} sats/vB
+
+
@@ -119,14 +153,36 @@
- - - - - - - -
+
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + +
+
+

diff --git a/frontend/src/app/components/transaction/transaction.component.scss b/frontend/src/app/components/transaction/transaction.component.scss index 66730e887..33c4353d9 100644 --- a/frontend/src/app/components/transaction/transaction.component.scss +++ b/frontend/src/app/components/transaction/transaction.component.scss @@ -4,19 +4,22 @@ .title-block { color: #FFF; - padding-left: 10px; padding-top: 20px; - padding-bottom: 3px; border-top: 5px solid #FFF; } -.title-block > h1 { - margin: 0; -} - - @media (max-width: 767.98px) { .mobile-bottomcol { margin-top: 15px; } } + +.td-width { + width: 175px; +} + +@media (max-width: 767.98px) { + .td-width { + width: 150px; + } +} diff --git a/frontend/src/app/components/transaction/transaction.component.ts b/frontend/src/app/components/transaction/transaction.component.ts index 6946b95eb..549d024bd 100644 --- a/frontend/src/app/components/transaction/transaction.component.ts +++ b/frontend/src/app/components/transaction/transaction.component.ts @@ -1,13 +1,14 @@ import { Component, OnInit, OnDestroy } from '@angular/core'; import { ElectrsApiService } from '../../services/electrs-api.service'; import { ActivatedRoute, ParamMap } from '@angular/router'; -import { switchMap, filter } from 'rxjs/operators'; +import { switchMap, filter, take } from 'rxjs/operators'; import { Transaction, Block } from '../../interfaces/electrs.interface'; import { of } from 'rxjs'; import { StateService } from '../../services/state.service'; import { WebsocketService } from '../../services/websocket.service'; import { AudioService } from 'src/app/services/audio.service'; import { ApiService } from 'src/app/services/api.service'; +import { MempoolBlock } from 'src/app/interfaces/websocket.interface'; @Component({ selector: 'app-transaction', @@ -20,13 +21,13 @@ export class TransactionComponent implements OnInit, OnDestroy { feeRating: number; overpaidTimes: number; medianFeeNeeded: number; + txInBlockIndex: number; isLoadingTx = true; error: any = undefined; latestBlock: Block; transactionTime = -1; rightPosition = 0; - blockDepth = 0; constructor( private route: ActivatedRoute, @@ -56,6 +57,7 @@ export class TransactionComponent implements OnInit, OnDestroy { .subscribe((tx: Transaction) => { this.tx = tx; this.isLoadingTx = false; + this.setMempoolBlocksSubscription(); if (!tx.status.confirmed) { this.websocketService.startTrackTransaction(tx.txid); @@ -91,6 +93,25 @@ export class TransactionComponent implements OnInit, OnDestroy { }); } + setMempoolBlocksSubscription() { + this.stateService.mempoolBlocks$ + .subscribe((mempoolBlocks) => { + if (!this.tx) { + return; + } + + const txFeePerVSize = this.tx.fee / (this.tx.weight / 4); + + for (const block of mempoolBlocks) { + for (let i = 0; i < block.feeRange.length - 1; i++) { + if (txFeePerVSize < block.feeRange[i + 1] && txFeePerVSize >= block.feeRange[i]) { + this.txInBlockIndex = mempoolBlocks.indexOf(block); + } + } + } + }); + } + getTransactionTime() { this.apiService.getTransactionTimes$([this.tx.txid]) .subscribe((transactionTimes) => { @@ -100,7 +121,10 @@ export class TransactionComponent implements OnInit, OnDestroy { findBlockAndSetFeeRating() { this.stateService.blocks$ - .pipe(filter((block) => block.height === this.tx.status.block_height)) + .pipe( + filter((block) => block.height === this.tx.status.block_height), + take(1) + ) .subscribe((block) => { const feePervByte = this.tx.fee / (this.tx.weight / 4); this.medianFeeNeeded = block.feeRange[Math.round(block.feeRange.length * 0.5)]; diff --git a/frontend/src/app/components/transactions-list/transactions-list.component.html b/frontend/src/app/components/transactions-list/transactions-list.component.html index aa45cb4f9..cce721bda 100644 --- a/frontend/src/app/components/transactions-list/transactions-list.component.html +++ b/frontend/src/app/components/transactions-list/transactions-list.component.html @@ -7,7 +7,7 @@
{{ tx.status.block_time * 1000 | date:'yyyy-MM-dd HH:mm' }} - ago + ago
diff --git a/frontend/src/styles.scss b/frontend/src/styles.scss index ee98672b3..bbe983877 100644 --- a/frontend/src/styles.scss +++ b/frontend/src/styles.scss @@ -324,6 +324,10 @@ tr { white-space: nowrap; } +h1, h2, h3 { + margin-bottom: 15px; +} + @media (min-width: 992px) { .lg-inline { display: inline-block;