diff --git a/frontend/src/app/components/transaction/transaction.component.html b/frontend/src/app/components/transaction/transaction.component.html
index 0ff82899f..f25e2a012 100644
--- a/frontend/src/app/components/transaction/transaction.component.html
+++ b/frontend/src/app/components/transaction/transaction.component.html
@@ -190,6 +190,24 @@
+
+
Diagram
+
+
+
+
+
+
+
24">
+
+
+
+
+
+
+
+
+
Inputs & Outputs
@@ -283,6 +301,36 @@
+
+
Diagram
+
+
+
+
+
+
Inputs & Outputs
diff --git a/frontend/src/app/components/transaction/transaction.component.scss b/frontend/src/app/components/transaction/transaction.component.scss
index 4628c35f9..ec514369f 100644
--- a/frontend/src/app/components/transaction/transaction.component.scss
+++ b/frontend/src/app/components/transaction/transaction.component.scss
@@ -73,6 +73,24 @@
}
}
+.graph-container {
+ position: relative;
+ width: 100%;
+ background: #181b2d;
+ padding: 10px;
+ padding-bottom: 0;
+}
+
+.toggle-wrapper {
+ width: 100%;
+ text-align: center;
+ margin: 1.25em 0 0;
+}
+
+.graph-toggle {
+ margin: auto;
+}
+
@media (max-width: 767.98px) {
.mobile-bottomcol {
margin-top: 15px;
diff --git a/frontend/src/app/components/transaction/transaction.component.ts b/frontend/src/app/components/transaction/transaction.component.ts
index 9a2629f08..be6460167 100644
--- a/frontend/src/app/components/transaction/transaction.component.ts
+++ b/frontend/src/app/components/transaction/transaction.component.ts
@@ -1,4 +1,4 @@
-import { Component, OnInit, OnDestroy } from '@angular/core';
+import { Component, OnInit, AfterViewInit, OnDestroy, HostListener, ViewChild, ElementRef } from '@angular/core';
import { ElectrsApiService } from '../../services/electrs-api.service';
import { ActivatedRoute, ParamMap } from '@angular/router';
import {
@@ -24,7 +24,7 @@ import { LiquidUnblinding } from './liquid-ublinding';
templateUrl: './transaction.component.html',
styleUrls: ['./transaction.component.scss'],
})
-export class TransactionComponent implements OnInit, OnDestroy {
+export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
network = '';
tx: Transaction;
txId: string;
@@ -47,6 +47,14 @@ export class TransactionComponent implements OnInit, OnDestroy {
timeAvg$: Observable;
liquidUnblinding = new LiquidUnblinding();
outputIndex: number;
+ graphExpanded: boolean = false;
+ graphWidth: number = 1000;
+ graphHeight: number = 360;
+ maxInOut: number = 0;
+ tooltipPosition: { x: number, y: number };
+
+ @ViewChild('graphContainer')
+ graphContainer: ElementRef;
constructor(
private route: ActivatedRoute,
@@ -167,6 +175,7 @@ export class TransactionComponent implements OnInit, OnDestroy {
this.waitingForTransaction = false;
this.setMempoolBlocksSubscription();
this.websocketService.startTrackTransaction(tx.txid);
+ this.setupGraph();
if (!tx.status.confirmed && tx.firstSeen) {
this.transactionTime = tx.firstSeen;
@@ -222,6 +231,10 @@ export class TransactionComponent implements OnInit, OnDestroy {
});
}
+ ngAfterViewInit(): void {
+ this.setGraphSize();
+ }
+
handleLoadElectrsTransactionError(error: any): Observable {
if (error.status === 404 && /^[a-fA-F0-9]{64}$/.test(this.txId)) {
this.websocketService.startMultiTrackTransaction(this.txId);
@@ -284,6 +297,26 @@ export class TransactionComponent implements OnInit, OnDestroy {
return +(cpfpTx.fee / (cpfpTx.weight / 4)).toFixed(1);
}
+ setupGraph() {
+ this.maxInOut = Math.min(250, Math.max(this.tx?.vin?.length || 1, this.tx?.vout?.length + 1 || 1));
+ this.graphHeight = Math.min(360, this.maxInOut * 80);
+ }
+
+ expandGraph() {
+ this.graphExpanded = true;
+ }
+
+ collapseGraph() {
+ this.graphExpanded = false;
+ }
+
+ @HostListener('window:resize', ['$event'])
+ setGraphSize(): void {
+ if (this.graphContainer) {
+ this.graphWidth = this.graphContainer.nativeElement.clientWidth - 24;
+ }
+ }
+
ngOnDestroy() {
this.subscription.unsubscribe();
this.fetchCpfpSubscription.unsubscribe();
diff --git a/frontend/src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.html b/frontend/src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.html
new file mode 100644
index 000000000..563e6ed00
--- /dev/null
+++ b/frontend/src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.html
@@ -0,0 +1,56 @@
+
diff --git a/frontend/src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.scss b/frontend/src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.scss
new file mode 100644
index 000000000..d0551f2c8
--- /dev/null
+++ b/frontend/src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.scss
@@ -0,0 +1,38 @@
+.bowtie-graph-tooltip {
+ position: absolute;
+ background: rgba(#11131f, 0.95);
+ border-radius: 4px;
+ box-shadow: 1px 1px 10px rgba(0,0,0,0.5);
+ color: #b1b1b1;
+ padding: 10px 15px;
+ text-align: left;
+ pointer-events: none;
+ max-width: 300px;
+
+ p {
+ margin: 0;
+ white-space: nowrap;
+ }
+
+ .address {
+ width: 100%;
+ max-width: 100%;
+ display: flex;
+ flex-direction: row;
+ align-items: baseline;
+ justify-content: flex-start;
+
+ .first {
+ flex-grow: 0;
+ flex-shrink: 1;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ margin-right: -2px;
+ }
+
+ .last-four {
+ flex-shrink: 0;
+ flex-grow: 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/frontend/src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.ts b/frontend/src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.ts
new file mode 100644
index 000000000..413bb68c0
--- /dev/null
+++ b/frontend/src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.ts
@@ -0,0 +1,48 @@
+import { Component, ElementRef, ViewChild, Input, OnChanges, ChangeDetectionStrategy } from '@angular/core';
+import { TransactionStripped } from 'src/app/interfaces/websocket.interface';
+
+interface Xput {
+ type: 'input' | 'output' | 'fee';
+ value?: number;
+ index?: number;
+ address?: string;
+ rest?: number;
+ coinbase?: boolean;
+ pegin?: boolean;
+ pegout?: string;
+ confidential?: boolean;
+}
+
+@Component({
+ selector: 'app-tx-bowtie-graph-tooltip',
+ templateUrl: './tx-bowtie-graph-tooltip.component.html',
+ styleUrls: ['./tx-bowtie-graph-tooltip.component.scss'],
+})
+export class TxBowtieGraphTooltipComponent implements OnChanges {
+ @Input() line: Xput | void;
+ @Input() cursorPosition: { x: number, y: number };
+
+ tooltipPosition = { x: 0, y: 0 };
+
+ @ViewChild('tooltip') tooltipElement: ElementRef;
+
+ constructor() {}
+
+ ngOnChanges(changes): void {
+ if (changes.cursorPosition && changes.cursorPosition.currentValue) {
+ let x = Math.max(10, changes.cursorPosition.currentValue.x - 50);
+ let y = changes.cursorPosition.currentValue.y + 20;
+ 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 };
+ }
+ }
+}
diff --git a/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.html b/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.html
index c4771c58c..03056cd53 100644
--- a/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.html
+++ b/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.html
@@ -1,44 +1,82 @@
-