Projected block overview mouse events & tx preview
This commit is contained in:
		
							parent
							
								
									d4c9f6decb
								
							
						
					
					
						commit
						1aac96a6f6
					
				@ -1,6 +1,6 @@
 | 
			
		||||
import TxSprite from './tx-sprite'
 | 
			
		||||
import TxView from './tx-view'
 | 
			
		||||
import { Square } from './sprite-types'
 | 
			
		||||
import { Position, Square } from './sprite-types'
 | 
			
		||||
 | 
			
		||||
export default class BlockScene {
 | 
			
		||||
  scene: { count: number, offset: { x: number, y: number}};
 | 
			
		||||
@ -33,6 +33,7 @@ export default class BlockScene {
 | 
			
		||||
    this.unitWidth = this.gridSize - (this.unitPadding * 2)
 | 
			
		||||
 | 
			
		||||
    this.dirty = true
 | 
			
		||||
    if (this.initialised && this.scene) this.updateAll(performance.now())
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Animate new block entering scene
 | 
			
		||||
@ -70,6 +71,14 @@ export default class BlockScene {
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  //return the tx at this screen position, if any
 | 
			
		||||
  getTxAt (position: Position): TxView | void {
 | 
			
		||||
    if (this.layout) {
 | 
			
		||||
      const gridPosition = this.screenToGrid(position)
 | 
			
		||||
      return this.layout.getTx(gridPosition)
 | 
			
		||||
    } else return null
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private init ({ width, height, resolution, blockLimit }: { width: number, height: number, resolution: number, blockLimit: number}): void {
 | 
			
		||||
    this.scene = {
 | 
			
		||||
      count: 0,
 | 
			
		||||
@ -205,6 +214,14 @@ export default class BlockScene {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  screenToGrid (position: Position): Position {
 | 
			
		||||
    const grid = {
 | 
			
		||||
      x: Math.floor((position.y - this.unitPadding) / this.gridSize),
 | 
			
		||||
      y: Math.floor((this.width + (this.unitPadding * 2) - position.x) / this.gridSize)
 | 
			
		||||
    }
 | 
			
		||||
    return grid
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // calculates and returns the size of the tx in multiples of the grid size
 | 
			
		||||
  private txSize (tx: TxView): number {
 | 
			
		||||
    let scale = Math.max(1,Math.round(Math.sqrt(tx.vsize / this.vbytesPerUnit)))
 | 
			
		||||
@ -328,6 +345,14 @@ class Row {
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  txAt (x: number): TxView | void {
 | 
			
		||||
    let i = 0
 | 
			
		||||
    while (i < this.filled.length && this.filled[i].l <= x) {
 | 
			
		||||
      if (this.filled[i].l <= x && this.filled[i].r > x) return this.filled[i].tx
 | 
			
		||||
      i++
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class BlockLayout {
 | 
			
		||||
@ -344,6 +369,16 @@ class BlockLayout {
 | 
			
		||||
    this.txPositions = {}
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getRow (position: Square): Row {
 | 
			
		||||
    return this.rows[position.y]
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getTx (position: Square): TxView | void {
 | 
			
		||||
    if (this.getRow(position)) {
 | 
			
		||||
      return this.getRow(position).txAt(position.x)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  addRow (): void {
 | 
			
		||||
    this.rows.push(new Row(this.rows.length, this.width))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
import { Component, ElementRef, ViewChild, HostListener, Input, OnInit, OnDestroy,  OnChanges, ChangeDetectionStrategy, NgZone } from '@angular/core';
 | 
			
		||||
import { Component, ElementRef, ViewChild, HostListener, Input, Output, EventEmitter, OnInit, OnDestroy,  OnChanges, ChangeDetectionStrategy, NgZone } from '@angular/core';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { MempoolBlockWithTransactions } from 'src/app/interfaces/websocket.interface';
 | 
			
		||||
import { MempoolBlockWithTransactions, TransactionStripped } from 'src/app/interfaces/websocket.interface';
 | 
			
		||||
import { Observable, Subscription } from 'rxjs';
 | 
			
		||||
import { WebsocketService } from 'src/app/services/websocket.service';
 | 
			
		||||
import { FastVertexArray } from './fast-vertex-array';
 | 
			
		||||
@ -16,6 +16,7 @@ import TxView from './tx-view';
 | 
			
		||||
})
 | 
			
		||||
export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChanges {
 | 
			
		||||
  @Input() index: number;
 | 
			
		||||
  @Output() txPreviewEvent = new EventEmitter<TransactionStripped | void>()
 | 
			
		||||
 | 
			
		||||
  @ViewChild('blockCanvas')
 | 
			
		||||
  canvas: ElementRef<HTMLCanvasElement>;
 | 
			
		||||
@ -29,6 +30,8 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang
 | 
			
		||||
  running: boolean;
 | 
			
		||||
  scene: BlockScene;
 | 
			
		||||
  txViews: { [key: string]: TxView };
 | 
			
		||||
  hoverTx: TxView | void;
 | 
			
		||||
  selectedTx: TxView | void;
 | 
			
		||||
  lastBlockHeight: number;
 | 
			
		||||
  blockIndex: number;
 | 
			
		||||
 | 
			
		||||
@ -259,6 +262,56 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang
 | 
			
		||||
      this.animationFrameRequest = requestAnimationFrame(() => this.run());
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @HostListener('click', ['$event'])
 | 
			
		||||
  onClick(event) {
 | 
			
		||||
    this.setPreviewTx(event.offsetX, event.offsetY, true)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @HostListener('pointermove', ['$event'])
 | 
			
		||||
  onPointerMove(event) {
 | 
			
		||||
    this.setPreviewTx(event.offsetX, event.offsetY, false)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @HostListener('pointerleave', ['$event'])
 | 
			
		||||
  onPointerLeave(event) {
 | 
			
		||||
    this.setPreviewTx(-1, -1, false)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setPreviewTx(x: number, y: number, clicked: boolean = false) {
 | 
			
		||||
    if (this.scene && (!this.selectedTx || clicked)) {
 | 
			
		||||
      const selected = this.scene.getTxAt({ x, y })
 | 
			
		||||
      const currentPreview = this.selectedTx || this.hoverTx
 | 
			
		||||
 | 
			
		||||
      if (selected !== currentPreview) {
 | 
			
		||||
        if (currentPreview) currentPreview.setHover(false)
 | 
			
		||||
        if (selected) {
 | 
			
		||||
          selected.setHover(true)
 | 
			
		||||
          this.txPreviewEvent.emit({
 | 
			
		||||
            txid: selected.txid,
 | 
			
		||||
            fee: selected.fee,
 | 
			
		||||
            vsize: selected.vsize,
 | 
			
		||||
            value: selected.value
 | 
			
		||||
          })
 | 
			
		||||
          if (clicked) this.selectedTx = selected
 | 
			
		||||
          else this.hoverTx = selected
 | 
			
		||||
        } else {
 | 
			
		||||
          if (clicked) {
 | 
			
		||||
            this.selectedTx = null
 | 
			
		||||
          }
 | 
			
		||||
          this.hoverTx = null
 | 
			
		||||
          this.txPreviewEvent.emit(null)
 | 
			
		||||
        }
 | 
			
		||||
      } else if (clicked) {
 | 
			
		||||
        if (selected === this.selectedTx) {
 | 
			
		||||
          this.hoverTx = this.selectedTx
 | 
			
		||||
          this.selectedTx = null
 | 
			
		||||
        } else {
 | 
			
		||||
          this.selectedTx = selected
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WebGL shader attributes
 | 
			
		||||
 | 
			
		||||
@ -10,39 +10,68 @@
 | 
			
		||||
  <div class="box">
 | 
			
		||||
    <div class="row">
 | 
			
		||||
      <div class="col-md">
 | 
			
		||||
        <table class="table table-borderless table-striped">
 | 
			
		||||
        <table class="table table-borderless table-striped table-fixed">
 | 
			
		||||
          <tbody>
 | 
			
		||||
            <tr>
 | 
			
		||||
              <td i18n="mempool-block.median-fee">Median fee</td>
 | 
			
		||||
              <td>~{{ mempoolBlock.medianFee | number:'1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span> <span class="fiat"><app-fiat [value]="mempoolBlock.medianFee * 140" digitsInfo="1.2-2" i18n-ngbTooltip="Transaction fee tooltip" ngbTooltip="Based on average native segwit transaction of 140 vBytes" placement="bottom"></app-fiat></span></td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            <tr>
 | 
			
		||||
              <td i18n="mempool-block.fee-span">Fee span</td>
 | 
			
		||||
              <td><span class="yellow-color">{{ mempoolBlock.feeRange[0] | number:'1.0-0' }} - {{ mempoolBlock.feeRange[mempoolBlock.feeRange.length - 1] | number:'1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></span></td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            <tr>
 | 
			
		||||
              <td i18n="block.total-fees|Total fees in a block">Total fees</td>
 | 
			
		||||
              <td><app-amount [satoshis]="mempoolBlock.totalFees" [digitsInfo]="'1.2-2'" [noFiat]="true"></app-amount> <span class="fiat"><app-fiat [value]="mempoolBlock.totalFees" digitsInfo="1.0-0"></app-fiat></span></td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            <tr>
 | 
			
		||||
              <td i18n="mempool-block.transactions">Transactions</td>
 | 
			
		||||
              <td>{{ mempoolBlock.nTx }}</td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            <tr>
 | 
			
		||||
              <td i18n="mempool-block.size">Size</td>
 | 
			
		||||
              <td>
 | 
			
		||||
                <div class="progress">
 | 
			
		||||
                  <div class="progress-bar progress-mempool {{ (network$ | async) }}" role="progressbar" [ngStyle]="{'width': (mempoolBlock.blockVSize / stateService.blockVSize) * 100 + '%' }"></div>
 | 
			
		||||
                  <div class="progress-text" [innerHTML]="mempoolBlock.blockSize | bytes: 2"></div>
 | 
			
		||||
                </div>
 | 
			
		||||
              </td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            <ng-container *ngIf="!previewTx">
 | 
			
		||||
              <tr>
 | 
			
		||||
                <td i18n="mempool-block.median-fee">Median fee</td>
 | 
			
		||||
                <td>~{{ mempoolBlock.medianFee | number:'1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span> <span class="fiat"><app-fiat [value]="mempoolBlock.medianFee * 140" digitsInfo="1.2-2" i18n-ngbTooltip="Transaction fee tooltip" ngbTooltip="Based on average native segwit transaction of 140 vBytes" placement="bottom"></app-fiat></span></td>
 | 
			
		||||
              </tr>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <td i18n="mempool-block.fee-span">Fee span</td>
 | 
			
		||||
                <td><span class="yellow-color">{{ mempoolBlock.feeRange[0] | number:'1.0-0' }} - {{ mempoolBlock.feeRange[mempoolBlock.feeRange.length - 1] | number:'1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></span></td>
 | 
			
		||||
              </tr>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <td i18n="block.total-fees|Total fees in a block">Total fees</td>
 | 
			
		||||
                <td><app-amount [satoshis]="mempoolBlock.totalFees" [digitsInfo]="'1.2-2'" [noFiat]="true"></app-amount> <span class="fiat"><app-fiat [value]="mempoolBlock.totalFees" digitsInfo="1.0-0"></app-fiat></span></td>
 | 
			
		||||
              </tr>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <td i18n="mempool-block.transactions">Transactions</td>
 | 
			
		||||
                <td>{{ mempoolBlock.nTx }}</td>
 | 
			
		||||
              </tr>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <td i18n="mempool-block.size">Size</td>
 | 
			
		||||
                <td>
 | 
			
		||||
                  <div class="progress">
 | 
			
		||||
                    <div class="progress-bar progress-mempool {{ (network$ | async) }}" role="progressbar" [ngStyle]="{'width': (mempoolBlock.blockVSize / stateService.blockVSize) * 100 + '%' }"></div>
 | 
			
		||||
                    <div class="progress-text" [innerHTML]="mempoolBlock.blockSize | bytes: 2"></div>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </td>
 | 
			
		||||
              </tr>
 | 
			
		||||
            </ng-container>
 | 
			
		||||
 | 
			
		||||
            <ng-container *ngIf="previewTx">
 | 
			
		||||
              <tr>
 | 
			
		||||
                <td i18n="shared.transaction">Transaction</td>
 | 
			
		||||
                <td>
 | 
			
		||||
                  <a [routerLink]="['/tx/' | relativeUrl, previewTx.txid]">{{ previewTx.txid | shortenString : 16}}</a>
 | 
			
		||||
                </td>
 | 
			
		||||
              </tr>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <td class="td-width" i18n="transaction.value|Transaction value">Value</td>
 | 
			
		||||
                <td><app-amount [satoshis]="previewTx.value"></app-amount></td>
 | 
			
		||||
              </tr>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <td class="td-width" i18n="transaction.fee|Transaction fee">Fee</td>
 | 
			
		||||
                <td>{{ previewTx.fee | number }} <span class="symbol" i18n="shared.sat|sat">sat</span> <span class="fiat"><app-fiat [value]="previewTx.fee"></app-fiat></span></td>
 | 
			
		||||
              </tr>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <td i18n="transaction.fee-rate|Transaction fee rate">Fee rate</td>
 | 
			
		||||
                <td>
 | 
			
		||||
                  {{ (previewTx.fee / previewTx.vsize) | feeRounding }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
 | 
			
		||||
                </td>
 | 
			
		||||
              </tr>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <td i18n="transaction.vsize|Transaction Virtual Size">Virtual size</td>
 | 
			
		||||
                <td [innerHTML]="'‎' + (previewTx.vsize | vbytes: 2)"></td>
 | 
			
		||||
              </tr>
 | 
			
		||||
              </ng-container>
 | 
			
		||||
          </tbody>
 | 
			
		||||
        </table>
 | 
			
		||||
        <app-fee-distribution-graph [data]="mempoolBlock.feeRange" ></app-fee-distribution-graph>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="col-md chart-container">
 | 
			
		||||
        <app-mempool-block-overview [index]="mempoolBlockIndex"></app-mempool-block-overview>
 | 
			
		||||
        <app-mempool-block-overview [index]="mempoolBlockIndex" (txPreviewEvent)="setTxPreview($event)"></app-mempool-block-overview>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,7 @@ import { Component, OnInit, OnDestroy, ChangeDetectionStrategy } from '@angular/
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { ActivatedRoute, ParamMap } from '@angular/router';
 | 
			
		||||
import { switchMap, map, tap, filter } from 'rxjs/operators';
 | 
			
		||||
import { MempoolBlock } from 'src/app/interfaces/websocket.interface';
 | 
			
		||||
import { MempoolBlock, TransactionStripped } from 'src/app/interfaces/websocket.interface';
 | 
			
		||||
import { Observable, BehaviorSubject } from 'rxjs';
 | 
			
		||||
import { SeoService } from 'src/app/services/seo.service';
 | 
			
		||||
import { WebsocketService } from 'src/app/services/websocket.service';
 | 
			
		||||
@ -18,6 +18,7 @@ export class MempoolBlockComponent implements OnInit, OnDestroy {
 | 
			
		||||
  mempoolBlockIndex: number;
 | 
			
		||||
  mempoolBlock$: Observable<MempoolBlock>;
 | 
			
		||||
  ordinal$: BehaviorSubject<string> = new BehaviorSubject('');
 | 
			
		||||
  previewTx: TransactionStripped | void;
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    private route: ActivatedRoute,
 | 
			
		||||
@ -74,5 +75,9 @@ export class MempoolBlockComponent implements OnInit, OnDestroy {
 | 
			
		||||
    } else {
 | 
			
		||||
      return $localize`:@@mempool-block.block.no:Mempool block ${this.mempoolBlockIndex + 1}:INTERPOLATION:`;
 | 
			
		||||
    }
 | 
			
		||||
 }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setTxPreview(event: TransactionStripped | void): void {
 | 
			
		||||
    this.previewTx = event
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -243,6 +243,10 @@ body {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.table-fixed {
 | 
			
		||||
  table-layout: fixed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.close {
 | 
			
		||||
  color: #fff;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user