diff --git a/frontend/src/app/components/mempool-block-overview/block-scene.ts b/frontend/src/app/components/mempool-block-overview/block-scene.ts index 343800dfc..f68901955 100644 --- a/frontend/src/app/components/mempool-block-overview/block-scene.ts +++ b/frontend/src/app/components/mempool-block-overview/block-scene.ts @@ -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)) } diff --git a/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.ts b/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.ts index 9f2b99034..b33efdb22 100644 --- a/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.ts +++ b/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.ts @@ -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() @ViewChild('blockCanvas') canvas: ElementRef; @@ -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 diff --git a/frontend/src/app/components/mempool-block/mempool-block.component.html b/frontend/src/app/components/mempool-block/mempool-block.component.html index 5c3f87102..9e8502ee5 100644 --- a/frontend/src/app/components/mempool-block/mempool-block.component.html +++ b/frontend/src/app/components/mempool-block/mempool-block.component.html @@ -10,39 +10,68 @@
- +
- - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Median fee~{{ mempoolBlock.medianFee | number:'1.0-0' }} sat/vB
Fee span{{ mempoolBlock.feeRange[0] | number:'1.0-0' }} - {{ mempoolBlock.feeRange[mempoolBlock.feeRange.length - 1] | number:'1.0-0' }} sat/vB
Total fees
Transactions{{ mempoolBlock.nTx }}
Size -
-
-
-
-
Median fee~{{ mempoolBlock.medianFee | number:'1.0-0' }} sat/vB
Fee span{{ mempoolBlock.feeRange[0] | number:'1.0-0' }} - {{ mempoolBlock.feeRange[mempoolBlock.feeRange.length - 1] | number:'1.0-0' }} sat/vB
Total fees
Transactions{{ mempoolBlock.nTx }}
Size +
+
+
+
+
Transaction + {{ previewTx.txid | shortenString : 16}} +
Value
Fee{{ previewTx.fee | number }} sat
Fee rate + {{ (previewTx.fee / previewTx.vsize) | feeRounding }} sat/vB +
Virtual size
- +
diff --git a/frontend/src/app/components/mempool-block/mempool-block.component.ts b/frontend/src/app/components/mempool-block/mempool-block.component.ts index 75147f5e3..bab26eb2e 100644 --- a/frontend/src/app/components/mempool-block/mempool-block.component.ts +++ b/frontend/src/app/components/mempool-block/mempool-block.component.ts @@ -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; ordinal$: BehaviorSubject = 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 + } } diff --git a/frontend/src/styles.scss b/frontend/src/styles.scss index 318cd03d5..a44d2c30d 100644 --- a/frontend/src/styles.scss +++ b/frontend/src/styles.scss @@ -243,6 +243,10 @@ body { } } +.table-fixed { + table-layout: fixed; +} + .close { color: #fff; }