Adding ETA, confirmed time, and other ui improvements to transaction page.
This commit is contained in:
		
							parent
							
								
									dc8eae6b76
								
							
						
					
					
						commit
						39643ce0f8
					
				@ -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,
 | 
			
		||||
 | 
			
		||||
@ -18,7 +18,7 @@
 | 
			
		||||
                <td>
 | 
			
		||||
                  {{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }}
 | 
			
		||||
                  <div class="lg-inline">
 | 
			
		||||
                    <i>(<app-time-since [time]="block.timestamp"></app-time-since> ago)</i>
 | 
			
		||||
                    <i>(<app-time-since [time]="block.timestamp" [fastRender]="true"></app-time-since> ago)</i>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </td>
 | 
			
		||||
              </tr>
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										44
									
								
								frontend/src/app/components/timespan/timespan.component.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								frontend/src/app/components/timespan/timespan.component.ts
									
									
									
									
									
										Normal file
									
								
							@ -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;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -17,44 +17,58 @@
 | 
			
		||||
      </a>
 | 
			
		||||
      <app-clipboard [text]="txId"></app-clipboard>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="clearfix"></div>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
  <br>
 | 
			
		||||
 | 
			
		||||
  <ng-template [ngIf]="!isLoadingTx && !error">
 | 
			
		||||
 | 
			
		||||
    <ng-template [ngIf]="tx.status.confirmed" [ngIfElse]="unconfirmedTemplate">
 | 
			
		||||
    
 | 
			
		||||
      <div class="box">
 | 
			
		||||
        
 | 
			
		||||
        <table class="table table-borderless table-striped">
 | 
			
		||||
          <tbody>
 | 
			
		||||
            <tr>
 | 
			
		||||
              <td>Included in block</td>
 | 
			
		||||
              <td>
 | 
			
		||||
                <a [routerLink]="['/block/', tx.status.block_hash]" [state]="{ data: { blockHeight: tx.status.block_height } }">{{ tx.status.block_height }}</a>
 | 
			
		||||
                <div class="md-inline"> at {{ tx.status.block_time * 1000 | date:'yyyy-MM-dd HH:mm' }}</div>
 | 
			
		||||
                <div class="md-inline"> <i>(<app-time-since [time]="tx.status.block_time"></app-time-since> ago)</i></div>
 | 
			
		||||
              </td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            <ng-template [ngIf]="tx.fee">
 | 
			
		||||
              <tr>
 | 
			
		||||
                <td>Fee per vByte</td>
 | 
			
		||||
                <td>
 | 
			
		||||
                  {{ tx.fee / (tx.weight / 4) | number : '1.2-2' }} sats/vB
 | 
			
		||||
                   
 | 
			
		||||
                  <span *ngIf="feeRating === 1" class="badge badge-success">Optimal</span>
 | 
			
		||||
                  <span *ngIf="feeRating === 2" class="badge badge-warning" title="Only ~{{ medianFeeNeeded }} sat/vB was needed to get into this block">Overpaid {{ overpaidTimes }}x</span>
 | 
			
		||||
                  <span *ngIf="feeRating === 3" class="badge badge-danger" title="Only ~{{ medianFeeNeeded }} sat/vB was needed to get into this block">Overpaid {{ overpaidTimes }}x</span>
 | 
			
		||||
                </td>
 | 
			
		||||
              </tr>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <td>Fee</td>
 | 
			
		||||
                <td>{{ tx.fee | number }} sats (<app-fiat [value]="tx.fee"></app-fiat>)</td>
 | 
			
		||||
              </tr>
 | 
			
		||||
            </ng-template>
 | 
			
		||||
          </tbody>
 | 
			
		||||
        </table>
 | 
			
		||||
        <div class="row">
 | 
			
		||||
          <div class="col-sm">
 | 
			
		||||
            <table class="table table-borderless table-striped">
 | 
			
		||||
              <tbody>
 | 
			
		||||
                <tr>
 | 
			
		||||
                  <td class="td-width">Included in block</td>
 | 
			
		||||
                  <td>
 | 
			
		||||
                    <a [routerLink]="['/block/', tx.status.block_hash]" [state]="{ data: { blockHeight: tx.status.block_height } }">{{ tx.status.block_height }}</a>
 | 
			
		||||
                    <i> (<app-time-since [time]="tx.status.block_time" [fastRender]="true"></app-time-since> ago)</i>
 | 
			
		||||
                  </td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                <ng-template [ngIf]="transactionTime > 0">
 | 
			
		||||
                  <tr>
 | 
			
		||||
                    <td>Confirmed</td>
 | 
			
		||||
                    <td>After <app-timespan [time]="tx.status.block_time - transactionTime"></app-timespan></td>
 | 
			
		||||
                  </tr>
 | 
			
		||||
                </ng-template>
 | 
			
		||||
              </tbody>
 | 
			
		||||
            </table>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="col-sm">
 | 
			
		||||
            <table class="table table-borderless table-striped">
 | 
			
		||||
              <tbody>
 | 
			
		||||
                <ng-template [ngIf]="tx.fee">
 | 
			
		||||
                  <tr>
 | 
			
		||||
                    <td class="td-width">Fee</td>
 | 
			
		||||
                    <td>{{ tx.fee | number }} sats (<app-fiat [value]="tx.fee"></app-fiat>)</td>
 | 
			
		||||
                  </tr>
 | 
			
		||||
                  <tr>
 | 
			
		||||
                    <td>Fee per vByte</td>
 | 
			
		||||
                    <td>
 | 
			
		||||
                      {{ tx.fee / (tx.weight / 4) | number : '1.1-1' }} sats/vB
 | 
			
		||||
                       
 | 
			
		||||
                      <span *ngIf="feeRating === 1" class="badge badge-success">Optimal</span>
 | 
			
		||||
                      <span *ngIf="feeRating === 2" class="badge badge-warning" title="Only ~{{ medianFeeNeeded }} sat/vB was needed to get into this block">Overpaid {{ overpaidTimes }}x</span>
 | 
			
		||||
                      <span *ngIf="feeRating === 3" class="badge badge-danger" title="Only ~{{ medianFeeNeeded }} sat/vB was needed to get into this block">Overpaid {{ overpaidTimes }}x</span>
 | 
			
		||||
                    </td>
 | 
			
		||||
                  </tr>
 | 
			
		||||
                </ng-template>
 | 
			
		||||
              </tbody>
 | 
			
		||||
            </table>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <br>
 | 
			
		||||
@ -64,31 +78,51 @@
 | 
			
		||||
    <ng-template #unconfirmedTemplate>
 | 
			
		||||
 | 
			
		||||
      <div class="box">
 | 
			
		||||
 | 
			
		||||
        <table class="table table-borderless table-striped">
 | 
			
		||||
          <tbody>
 | 
			
		||||
            <ng-template [ngIf]="transactionTime !== 0">
 | 
			
		||||
              <tr *ngIf="transactionTime === -1; else firstSeenTmpl">
 | 
			
		||||
                <td><span class="skeleton-loader"></span></td>
 | 
			
		||||
                <td><span class="skeleton-loader"></span></td>
 | 
			
		||||
              </tr>
 | 
			
		||||
              <ng-template #firstSeenTmpl>
 | 
			
		||||
        <div class="row">
 | 
			
		||||
          <div class="col-sm">
 | 
			
		||||
            <table class="table table-borderless table-striped">
 | 
			
		||||
              <tbody>
 | 
			
		||||
                <ng-template [ngIf]="transactionTime !== 0">
 | 
			
		||||
                  <tr *ngIf="transactionTime === -1; else firstSeenTmpl">
 | 
			
		||||
                    <td><span class="skeleton-loader"></span></td>
 | 
			
		||||
                    <td><span class="skeleton-loader"></span></td>
 | 
			
		||||
                  </tr>
 | 
			
		||||
                  <ng-template #firstSeenTmpl>
 | 
			
		||||
                    <tr>
 | 
			
		||||
                      <td>First seen</td>
 | 
			
		||||
                      <td><i><app-time-since [time]="transactionTime" [fastRender]="true"></app-time-since> ago</i></td>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                  </ng-template>
 | 
			
		||||
                </ng-template>
 | 
			
		||||
                <tr>
 | 
			
		||||
                  <td>First seen</td>
 | 
			
		||||
                  <td><i><app-time-since [time]="transactionTime"></app-time-since> ago</i></td>
 | 
			
		||||
                  <td class="td-width">ETA</td>
 | 
			
		||||
                  <td>
 | 
			
		||||
                    <ng-template [ngIf]="txInBlockIndex === undefined" [ngIfElse]="estimationTmpl">
 | 
			
		||||
                      <span class="skeleton-loader"></span>
 | 
			
		||||
                    </ng-template>
 | 
			
		||||
                    <ng-template #estimationTmpl>
 | 
			
		||||
                      ~{{ 10 * txInBlockIndex + 10 }} minutes <i>({{ txInBlockIndex + 1 }} block{{ txInBlockIndex > 0 ? 's' : '' }})</i>
 | 
			
		||||
                    </ng-template>
 | 
			
		||||
                  </td>
 | 
			
		||||
                </tr>
 | 
			
		||||
              </ng-template>
 | 
			
		||||
            </ng-template>
 | 
			
		||||
            <tr>
 | 
			
		||||
              <td>Fees</td>
 | 
			
		||||
              <td>{{ tx.fee | number }} sats (<app-fiat [value]="tx.fee"></app-fiat>)</td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            <tr>
 | 
			
		||||
              <td>Fees per vByte</td>
 | 
			
		||||
              <td>{{ tx.fee / (tx.weight / 4) | number : '1.2-2' }} sats/vB</td>
 | 
			
		||||
            </tr>
 | 
			
		||||
          </tbody>
 | 
			
		||||
        </table>
 | 
			
		||||
              </tbody>
 | 
			
		||||
            </table>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="col-sm">
 | 
			
		||||
            <table class="table table-borderless table-striped">
 | 
			
		||||
              <tbody>
 | 
			
		||||
                <tr>
 | 
			
		||||
                  <td class="td-width">Fee</td>
 | 
			
		||||
                  <td>{{ tx.fee | number }} sats (<app-fiat [value]="tx.fee"></app-fiat>)</td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                <tr>
 | 
			
		||||
                  <td>Fee per vByte</td>
 | 
			
		||||
                  <td>{{ tx.fee / (tx.weight / 4) | number : '1.1-1' }} sats/vB</td>
 | 
			
		||||
                </tr>
 | 
			
		||||
              </tbody>
 | 
			
		||||
            </table>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </ng-template>
 | 
			
		||||
 | 
			
		||||
@ -119,14 +153,36 @@
 | 
			
		||||
  <ng-template [ngIf]="isLoadingTx && !error">
 | 
			
		||||
 | 
			
		||||
    <div class="box">
 | 
			
		||||
      <table class="table table-borderless table-striped">
 | 
			
		||||
        <tbody>
 | 
			
		||||
          <tr>
 | 
			
		||||
            <td><span class="skeleton-loader"></span></td>
 | 
			
		||||
            <td><span class="skeleton-loader"></span></td>
 | 
			
		||||
          </tr>
 | 
			
		||||
        </tbody>
 | 
			
		||||
      </table>
 | 
			
		||||
      <div class="row">
 | 
			
		||||
        <div class="col-sm">
 | 
			
		||||
          <table class="table table-borderless table-striped">
 | 
			
		||||
            <tbody>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <td class="td-width"><span class="skeleton-loader"></span></td>
 | 
			
		||||
                <td><span class="skeleton-loader"></span></td>
 | 
			
		||||
              </tr>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <td><span class="skeleton-loader"></span></td>
 | 
			
		||||
                <td><span class="skeleton-loader"></span></td>
 | 
			
		||||
              </tr>
 | 
			
		||||
            </tbody>
 | 
			
		||||
          </table>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="col-sm">
 | 
			
		||||
          <table class="table table-borderless table-striped">
 | 
			
		||||
            <tbody>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <td class="td-width"><span class="skeleton-loader"></span></td>
 | 
			
		||||
                <td><span class="skeleton-loader"></span></td>
 | 
			
		||||
              </tr>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <td><span class="skeleton-loader"></span></td>
 | 
			
		||||
                <td><span class="skeleton-loader"></span></td>
 | 
			
		||||
              </tr>
 | 
			
		||||
            </tbody>
 | 
			
		||||
          </table>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <br>
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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)];
 | 
			
		||||
 | 
			
		||||
@ -7,7 +7,7 @@
 | 
			
		||||
    <div class="float-right">
 | 
			
		||||
      <ng-template [ngIf]="tx.status.confirmed">{{ tx.status.block_time * 1000 | date:'yyyy-MM-dd HH:mm' }}</ng-template>
 | 
			
		||||
      <ng-template [ngIf]="!tx.status.confirmed && tx.firstSeen">
 | 
			
		||||
        <i><app-time-since [time]="tx.firstSeen"></app-time-since> ago</i>
 | 
			
		||||
        <i><app-time-since [time]="tx.firstSeen" [fastRender]="true"></app-time-since> ago</i>
 | 
			
		||||
      </ng-template>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
@ -324,6 +324,10 @@ tr {
 | 
			
		||||
  white-space: nowrap;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
h1, h2, h3 {
 | 
			
		||||
  margin-bottom: 15px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (min-width: 992px) {
 | 
			
		||||
  .lg-inline {
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user