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 @@
+
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,