From 2d529bd5816553484b778a35f2b553ae89d142b8 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Wed, 15 Jun 2022 01:40:05 +0000 Subject: [PATCH] Tooltip-style tx previews in block overview --- .../block-overview-graph.component.html | 8 +- .../block-overview-graph.component.scss | 4 + .../block-overview-graph.component.ts | 55 ++++++++++--- .../block-overview-tooltip.component.html | 37 +++++++++ .../block-overview-tooltip.component.scss | 18 +++++ .../block-overview-tooltip.component.ts | 53 ++++++++++++ .../app/components/block/block.component.html | 1 + .../app/components/block/block.component.ts | 5 ++ .../mempool-block-overview.component.html | 2 +- .../mempool-block-overview.component.ts | 10 ++- .../mempool-block.component.html | 81 ++++++------------- frontend/src/app/shared/shared.module.ts | 3 + 12 files changed, 204 insertions(+), 73 deletions(-) create mode 100644 frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.html create mode 100644 frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.scss create mode 100644 frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.ts diff --git a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.html b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.html index 017eeab99..517eab653 100644 --- a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.html +++ b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.html @@ -1,6 +1,12 @@
- +
+ +
diff --git a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.scss b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.scss index cbb95ec0e..05b9b340a 100644 --- a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.scss +++ b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.scss @@ -18,6 +18,10 @@ width: 100%; height: 100%; overflow: hidden; + + &.clickable { + cursor: pointer; + } } .loader-wrapper { diff --git a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts index 953ed5715..a458ebd5f 100644 --- a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts +++ b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts @@ -5,6 +5,7 @@ import { FastVertexArray } from './fast-vertex-array'; import BlockScene from './block-scene'; import TxSprite from './tx-sprite'; import TxView from './tx-view'; +import { Position } from './sprite-types'; @Component({ selector: 'app-block-overview-graph', @@ -17,7 +18,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy { @Input() blockLimit: number; @Input() orientation = 'left'; @Input() flip = true; - @Output() txPreviewEvent = new EventEmitter(); + @Output() txClickEvent = new EventEmitter(); @ViewChild('blockCanvas') canvas: ElementRef; @@ -35,9 +36,11 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy { scene: BlockScene; hoverTx: TxView | void; selectedTx: TxView | void; + tooltipPosition: Position; constructor( readonly ngZone: NgZone, + readonly elRef: ElementRef, ) { this.vertexArray = new FastVertexArray(512, TxSprite.dataSize); } @@ -62,7 +65,6 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy { this.exit(direction); this.hoverTx = null; this.selectedTx = null; - this.txPreviewEvent.emit(null); this.start(); } @@ -257,25 +259,50 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy { } } - @HostListener('click', ['$event']) + @HostListener('document:click', ['$event']) + clickAway(event) { + if (!this.elRef.nativeElement.contains(event.target)) { + const currentPreview = this.selectedTx || this.hoverTx; + if (currentPreview && this.scene) { + this.scene.setHover(currentPreview, false); + this.start(); + } + this.hoverTx = null; + this.selectedTx = null; + } + } + + @HostListener('pointerup', ['$event']) onClick(event) { - this.setPreviewTx(event.offsetX, event.offsetY, true); + if (event.target === this.canvas.nativeElement && event.pointerType === 'touch') { + this.setPreviewTx(event.offsetX, event.offsetY, true); + } else if (event.target === this.canvas.nativeElement) { + this.onTxClick(event.offsetX, event.offsetY); + } } @HostListener('pointermove', ['$event']) onPointerMove(event) { - this.setPreviewTx(event.offsetX, event.offsetY, false); + if (event.target === this.canvas.nativeElement) { + this.setPreviewTx(event.offsetX, event.offsetY, false); + } } @HostListener('pointerleave', ['$event']) onPointerLeave(event) { - this.setPreviewTx(-1, -1, false); + if (event.pointerType !== 'touch') { + this.setPreviewTx(-1, -1, true); + } } setPreviewTx(cssX: number, cssY: number, clicked: boolean = false) { const x = cssX * window.devicePixelRatio; const y = cssY * window.devicePixelRatio; if (this.scene && (!this.selectedTx || clicked)) { + this.tooltipPosition = { + x: cssX, + y: cssY + }; const selected = this.scene.getTxAt({ x, y }); const currentPreview = this.selectedTx || this.hoverTx; @@ -289,12 +316,6 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy { this.scene.setHover(selected, true); this.start(); } - this.txPreviewEvent.emit({ - txid: selected.txid, - fee: selected.fee, - vsize: selected.vsize, - value: selected.value - }); if (clicked) { this.selectedTx = selected; } else { @@ -305,7 +326,6 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy { this.selectedTx = null; } this.hoverTx = null; - this.txPreviewEvent.emit(null); } } else if (clicked) { if (selected === this.selectedTx) { @@ -317,6 +337,15 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy { } } } + + onTxClick(cssX: number, cssY: number) { + const x = cssX * window.devicePixelRatio; + const y = cssY * window.devicePixelRatio; + const selected = this.scene.getTxAt({ x, y }); + if (selected && selected.txid) { + this.txClickEvent.emit(selected); + } + } } // WebGL shader attributes diff --git a/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.html b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.html new file mode 100644 index 000000000..431e2d1d9 --- /dev/null +++ b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.html @@ -0,0 +1,37 @@ +
+ + + + + + + + + + + + + + + + + + + + + + + +
Transaction + {{ txid | shortenString : 16}} +
Value
Fee{{ fee | number }} sat  
Fee rate + {{ feeRate | feeRounding }} sat/vB +
Virtual size
+
diff --git a/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.scss b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.scss new file mode 100644 index 000000000..440885750 --- /dev/null +++ b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.scss @@ -0,0 +1,18 @@ +.block-overview-tooltip { + position: absolute; + background: rgba(#11131f, 0.95); + border-radius: 4px; + box-shadow: 1px 1px 10px rgba(0,0,0,0.5); + color: #b1b1b1; + display: flex; + flex-direction: column; + justify-content: space-between; + padding: 10px 15px; + text-align: left; + width: 320px; + pointer-events: none; + + &.clickable { + pointer-events: all; + } +} diff --git a/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.ts b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.ts new file mode 100644 index 000000000..603b5fcdb --- /dev/null +++ b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.ts @@ -0,0 +1,53 @@ +import { Component, ElementRef, ViewChild, Input, OnChanges, ChangeDetectionStrategy } from '@angular/core'; +import { TransactionStripped } from 'src/app/interfaces/websocket.interface'; +import { Position } from 'src/app/components/block-overview-graph/sprite-types.js'; + +@Component({ + selector: 'app-block-overview-tooltip', + templateUrl: './block-overview-tooltip.component.html', + styleUrls: ['./block-overview-tooltip.component.scss'], +}) +export class BlockOverviewTooltipComponent implements OnChanges { + @Input() tx: TransactionStripped | void; + @Input() cursorPosition: Position; + @Input() clickable: boolean; + + txid = ''; + fee = 0; + value = 0; + vsize = 1; + feeRate = 0; + + tooltipPosition: Position = { x: 0, y: 0 }; + + @ViewChild('tooltip') tooltipElement: ElementRef; + + constructor() {} + + ngOnChanges(changes): void { + if (changes.cursorPosition && changes.cursorPosition.currentValue) { + let x = changes.cursorPosition.currentValue.x + 10; + let y = changes.cursorPosition.currentValue.y + 10; + if (this.tooltipElement) { + const elementBounds = this.tooltipElement.nativeElement.getBoundingClientRect(); + const parentBounds = this.tooltipElement.nativeElement.offsetParent.getBoundingClientRect(); + if ((parentBounds.left + x + elementBounds.width) > parentBounds.right) { + x = Math.max(0, parentBounds.width - elementBounds.width - 10); + } + if (y + elementBounds.height > parentBounds.height) { + y = y - elementBounds.height - 20; + } + } + this.tooltipPosition = { x, y }; + } + + if (changes.tx) { + const tx = changes.tx.currentValue || {}; + this.txid = tx.txid || ''; + this.fee = tx.fee || 0; + this.value = tx.value || 0; + this.vsize = tx.vsize || 1; + this.feeRate = this.fee / this.vsize; + } + } +} diff --git a/frontend/src/app/components/block/block.component.html b/frontend/src/app/components/block/block.component.html index fdf5caf4e..1216ecc30 100644 --- a/frontend/src/app/components/block/block.component.html +++ b/frontend/src/app/components/block/block.component.html @@ -249,6 +249,7 @@ [blockLimit]="stateService.blockVSize" [orientation]="'top'" [flip]="false" + (txClickEvent)="onTxClick($event)" > diff --git a/frontend/src/app/components/block/block.component.ts b/frontend/src/app/components/block/block.component.ts index 6394a624e..39c4042fb 100644 --- a/frontend/src/app/components/block/block.component.ts +++ b/frontend/src/app/components/block/block.component.ts @@ -367,6 +367,11 @@ export class BlockComponent implements OnInit, OnDestroy { } } } + + onTxClick(event: TransactionStripped): void { + const url = new RelativeUrlPipe(this.stateService).transform(`/tx/${event.txid}`); + this.router.navigate([url]); + } } function detectWebGL() { diff --git a/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.html b/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.html index 2b6ff37a5..304b2a7f9 100644 --- a/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.html +++ b/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.html @@ -5,5 +5,5 @@ [blockLimit]="stateService.blockVSize" [orientation]="'left'" [flip]="true" - (txPreviewEvent)="onTxPreview($event)" + (txClickEvent)="onTxClick($event)" > 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 aed949af3..bd78b13a9 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 @@ -6,6 +6,8 @@ import { BlockOverviewGraphComponent } from 'src/app/components/block-overview-g import { Subscription, BehaviorSubject, merge, of } from 'rxjs'; import { switchMap, filter } from 'rxjs/operators'; import { WebsocketService } from 'src/app/services/websocket.service'; +import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.pipe'; +import { Router } from '@angular/router'; @Component({ selector: 'app-mempool-block-overview', @@ -27,7 +29,8 @@ export class MempoolBlockOverviewComponent implements OnDestroy, OnChanges, Afte constructor( public stateService: StateService, - private websocketService: WebsocketService + private websocketService: WebsocketService, + private router: Router, ) { } ngAfterViewInit(): void { @@ -89,7 +92,8 @@ export class MempoolBlockOverviewComponent implements OnDestroy, OnChanges, Afte this.isLoading$.next(false); } - onTxPreview(event: TransactionStripped | void): void { - this.txPreviewEvent.emit(event); + onTxClick(event: TransactionStripped): void { + const url = new RelativeUrlPipe(this.stateService).transform(`/tx/${event.txid}`); + this.router.navigate([url]); } } 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 da1e91f1b..3626e6ff5 100644 --- a/frontend/src/app/components/mempool-block/mempool-block.component.html +++ b/frontend/src/app/components/mempool-block/mempool-block.component.html @@ -10,62 +10,33 @@
- +
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + +
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
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 +
+
+
+
+
diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts index 5886be5d3..1a799086b 100644 --- a/frontend/src/app/shared/shared.module.ts +++ b/frontend/src/app/shared/shared.module.ts @@ -46,6 +46,7 @@ import { TransactionComponent } from '../components/transaction/transaction.comp import { TransactionsListComponent } from '../components/transactions-list/transactions-list.component'; import { BlockComponent } from '../components/block/block.component'; import { BlockOverviewGraphComponent } from '../components/block-overview-graph/block-overview-graph.component'; +import { BlockOverviewTooltipComponent } from '../components/block-overview-tooltip/block-overview-tooltip.component'; import { AddressComponent } from '../components/address/address.component'; import { SearchFormComponent } from '../components/search-form/search-form.component'; import { AddressLabelsComponent } from '../components/address-labels/address-labels.component'; @@ -112,6 +113,7 @@ import { SvgImagesComponent } from '../components/svg-images/svg-images.componen TransactionComponent, BlockComponent, BlockOverviewGraphComponent, + BlockOverviewTooltipComponent, TransactionsListComponent, AddressComponent, SearchFormComponent, @@ -206,6 +208,7 @@ import { SvgImagesComponent } from '../components/svg-images/svg-images.componen TransactionComponent, BlockComponent, BlockOverviewGraphComponent, + BlockOverviewTooltipComponent, TransactionsListComponent, AddressComponent, SearchFormComponent,