Scroll to input/output when clicked in tx diagram
This commit is contained in:
parent
54c44565fb
commit
c10ace8fb5
@ -208,7 +208,10 @@
|
||||
[lineLimit]="inOutLimit"
|
||||
[maxStrands]="graphExpanded ? maxInOut : 24"
|
||||
[network]="network"
|
||||
[tooltip]="true">
|
||||
[tooltip]="true"
|
||||
(selectInput)="selectInput($event)"
|
||||
(selectOutput)="selectOutput($event)"
|
||||
>
|
||||
</tx-bowtie-graph>
|
||||
</div>
|
||||
<div class="toggle-wrapper" *ngIf="maxInOut > 24">
|
||||
@ -240,7 +243,7 @@
|
||||
</div>
|
||||
|
||||
|
||||
<app-transactions-list #txList [transactions]="[tx]" [errorUnblinded]="errorUnblinded" [outputIndex]="outputIndex" [transactionPage]="true"></app-transactions-list>
|
||||
<app-transactions-list #txList [transactions]="[tx]" [errorUnblinded]="errorUnblinded" [inputIndex]="inputIndex" [outputIndex]="outputIndex" [transactionPage]="true"></app-transactions-list>
|
||||
|
||||
<div class="title text-left">
|
||||
<h2 i18n="transaction.details">Details</h2>
|
||||
|
@ -47,6 +47,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
now = new Date().getTime();
|
||||
timeAvg$: Observable<number>;
|
||||
liquidUnblinding = new LiquidUnblinding();
|
||||
inputIndex: number;
|
||||
outputIndex: number;
|
||||
showFlow: boolean = true;
|
||||
graphExpanded: boolean = false;
|
||||
@ -334,6 +335,16 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
this.graphExpanded = false;
|
||||
}
|
||||
|
||||
selectInput(input) {
|
||||
this.inputIndex = input;
|
||||
this.outputIndex = null;
|
||||
}
|
||||
|
||||
selectOutput(output) {
|
||||
this.outputIndex = output;
|
||||
this.inputIndex = null;
|
||||
}
|
||||
|
||||
@HostListener('window:resize', ['$event'])
|
||||
setGraphSize(): void {
|
||||
if (this.graphContainer) {
|
||||
|
@ -20,9 +20,9 @@
|
||||
<div class="col">
|
||||
<table class="table table-borderless smaller-text table-sm table-tx-vin">
|
||||
<tbody>
|
||||
<ng-template ngFor let-vin let-vindex="index" [ngForOf]="tx['@vinLimit'] ? ((tx.vin.length > rowLimit) ? tx.vin.slice(0, rowLimit - 2) : tx.vin.slice(0, rowLimit)) : tx.vin" [ngForTrackBy]="trackByIndexFn">
|
||||
<ng-template ngFor let-vin let-vindex="index" [ngForOf]="tx['@vinLimit'] ? ((tx.vin.length > inputRowLimit) ? tx.vin.slice(0, inputRowLimit - 2) : tx.vin.slice(0, inputRowLimit)) : tx.vin" [ngForTrackBy]="trackByIndexFn">
|
||||
<tr [ngClass]="{
|
||||
'assetBox': assetsMinimal && vin.prevout && assetsMinimal[vin.prevout.asset] && !vin.is_coinbase && vin.prevout.scriptpubkey_address && tx._unblinded,
|
||||
'assetBox': (assetsMinimal && vin.prevout && assetsMinimal[vin.prevout.asset] && !vin.is_coinbase && vin.prevout.scriptpubkey_address && tx._unblinded) || inputIndex === vindex,
|
||||
'highlight': vin.prevout?.scriptpubkey_address === this.address && this.address !== ''
|
||||
}">
|
||||
<td class="arrow-td">
|
||||
@ -146,7 +146,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
<tr *ngIf="tx.vin.length > rowLimit && tx['@vinLimit']">
|
||||
<tr *ngIf="tx.vin.length > inputRowLimit && tx['@vinLimit']">
|
||||
<td colspan="3" class="text-center">
|
||||
<button class="btn btn-sm btn-primary mt-2" (click)="loadMoreInputs(tx);"><span i18n="show-all">Show all</span> ({{ tx.vin.length }})</button>
|
||||
</td>
|
||||
@ -158,7 +158,7 @@
|
||||
<div class="col mobile-bottomcol">
|
||||
<table class="table table-borderless smaller-text table-sm table-tx-vout">
|
||||
<tbody>
|
||||
<ng-template ngFor let-vout let-vindex="index" [ngForOf]="tx['@voutLimit'] && !outputIndex ? ((tx.vout.length > rowLimit) ? tx.vout.slice(0, rowLimit - 2) : tx.vout.slice(0, rowLimit)) : tx.vout" [ngForTrackBy]="trackByIndexFn">
|
||||
<ng-template ngFor let-vout let-vindex="index" [ngForOf]="tx['@voutLimit'] ? ((tx.vout.length > outputRowLimit) ? tx.vout.slice(0, outputRowLimit - 2) : tx.vout.slice(0, outputRowLimit)) : tx.vout" [ngForTrackBy]="trackByIndexFn">
|
||||
<tr [ngClass]="{
|
||||
'assetBox': assetsMinimal && assetsMinimal[vout.asset] && vout.scriptpubkey_address && tx.vin && !tx.vin[0].is_coinbase && tx._unblinded || outputIndex === vindex,
|
||||
'highlight': vout.scriptpubkey_address === this.address && this.address !== ''
|
||||
@ -257,7 +257,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
<tr *ngIf="tx.vout.length > rowLimit && tx['@voutLimit'] && !outputIndex">
|
||||
<tr *ngIf="tx.vout.length > outputRowLimit && tx['@voutLimit']">
|
||||
<td colspan="3" class="text-center">
|
||||
<button class="btn btn-sm btn-primary mt-2" (click)="tx['@voutLimit'] = false;"><span i18n="show-all">Show all</span> ({{ tx.vout.length }})</button>
|
||||
</td>
|
||||
|
@ -24,6 +24,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
||||
@Input() transactionPage = false;
|
||||
@Input() errorUnblinded = false;
|
||||
@Input() paginated = false;
|
||||
@Input() inputIndex: number;
|
||||
@Input() outputIndex: number;
|
||||
@Input() address: string = '';
|
||||
@Input() rowLimit = 12;
|
||||
@ -37,6 +38,8 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
||||
showDetails$ = new BehaviorSubject<boolean>(false);
|
||||
assetsMinimal: any;
|
||||
transactionsLength: number = 0;
|
||||
inputRowLimit: number = 12;
|
||||
outputRowLimit: number = 12;
|
||||
|
||||
constructor(
|
||||
public stateService: StateService,
|
||||
@ -97,50 +100,57 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
||||
).subscribe(() => this.ref.markForCheck());
|
||||
}
|
||||
|
||||
ngOnChanges(): void {
|
||||
if (!this.transactions || !this.transactions.length) {
|
||||
return;
|
||||
ngOnChanges(changes): void {
|
||||
if (changes.inputIndex || changes.outputIndex || changes.rowLimit) {
|
||||
this.inputRowLimit = Math.max(this.rowLimit, (this.inputIndex || 0) + 3);
|
||||
this.outputRowLimit = Math.max(this.rowLimit, (this.outputIndex || 0) + 3);
|
||||
if (this.inputIndex || this.outputIndex) {
|
||||
setTimeout(() => {
|
||||
const assetBoxElements = document.getElementsByClassName('assetBox');
|
||||
if (assetBoxElements && assetBoxElements[0]) {
|
||||
assetBoxElements[0].scrollIntoView({block: "center"});
|
||||
}
|
||||
}, 10);
|
||||
}
|
||||
}
|
||||
|
||||
this.transactionsLength = this.transactions.length;
|
||||
if (this.outputIndex) {
|
||||
setTimeout(() => {
|
||||
const assetBoxElements = document.getElementsByClassName('assetBox');
|
||||
if (assetBoxElements && assetBoxElements[0]) {
|
||||
assetBoxElements[0].scrollIntoView();
|
||||
}
|
||||
}, 10);
|
||||
}
|
||||
|
||||
this.transactions.forEach((tx) => {
|
||||
tx['@voutLimit'] = true;
|
||||
tx['@vinLimit'] = true;
|
||||
if (tx['addressValue'] !== undefined) {
|
||||
if (changes.transactions || changes.address) {
|
||||
if (!this.transactions || !this.transactions.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.address) {
|
||||
const addressIn = tx.vout
|
||||
.filter((v: Vout) => v.scriptpubkey_address === this.address)
|
||||
.map((v: Vout) => v.value || 0)
|
||||
.reduce((a: number, b: number) => a + b, 0);
|
||||
this.transactionsLength = this.transactions.length;
|
||||
|
||||
const addressOut = tx.vin
|
||||
.filter((v: Vin) => v.prevout && v.prevout.scriptpubkey_address === this.address)
|
||||
.map((v: Vin) => v.prevout.value || 0)
|
||||
.reduce((a: number, b: number) => a + b, 0);
|
||||
|
||||
tx['addressValue'] = addressIn - addressOut;
|
||||
}
|
||||
});
|
||||
const txIds = this.transactions.filter((tx) => !tx._outspends).map((tx) => tx.txid);
|
||||
if (txIds.length) {
|
||||
this.refreshOutspends$.next(txIds);
|
||||
}
|
||||
if (this.stateService.env.LIGHTNING) {
|
||||
const txIds = this.transactions.filter((tx) => !tx._channels).map((tx) => tx.txid);
|
||||
this.transactions.forEach((tx) => {
|
||||
tx['@voutLimit'] = true;
|
||||
tx['@vinLimit'] = true;
|
||||
if (tx['addressValue'] !== undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.address) {
|
||||
const addressIn = tx.vout
|
||||
.filter((v: Vout) => v.scriptpubkey_address === this.address)
|
||||
.map((v: Vout) => v.value || 0)
|
||||
.reduce((a: number, b: number) => a + b, 0);
|
||||
|
||||
const addressOut = tx.vin
|
||||
.filter((v: Vin) => v.prevout && v.prevout.scriptpubkey_address === this.address)
|
||||
.map((v: Vin) => v.prevout.value || 0)
|
||||
.reduce((a: number, b: number) => a + b, 0);
|
||||
|
||||
tx['addressValue'] = addressIn - addressOut;
|
||||
}
|
||||
});
|
||||
const txIds = this.transactions.filter((tx) => !tx._outspends).map((tx) => tx.txid);
|
||||
if (txIds.length) {
|
||||
this.refreshChannels$.next(txIds);
|
||||
this.refreshOutspends$.next(txIds);
|
||||
}
|
||||
if (this.stateService.env.LIGHTNING) {
|
||||
const txIds = this.transactions.filter((tx) => !tx._channels).map((tx) => tx.txid);
|
||||
if (txIds.length) {
|
||||
this.refreshChannels$.next(txIds);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +44,7 @@
|
||||
<span *ngSwitchCase="'output'" i18n="transaction.output">Output</span>
|
||||
<span *ngSwitchCase="'fee'" i18n="transaction.fee">Fee</span>
|
||||
</ng-container>
|
||||
<span *ngIf="line.type !== 'fee'"> #{{ line.index }}</span>
|
||||
<span *ngIf="line.type !== 'fee'"> #{{ line.index + 1 }}</span>
|
||||
</p>
|
||||
<p *ngIf="line.value == null && line.confidential" i18n="shared.confidential">Confidential</p>
|
||||
<p *ngIf="line.value != null"><app-amount [satoshis]="line.value"></app-amount></p>
|
||||
|
@ -60,6 +60,7 @@
|
||||
attr.marker-start="url(#{{input.class}}-arrow)"
|
||||
(pointerover)="onHover($event, 'input', i);"
|
||||
(pointerout)="onBlur($event, 'input', i);"
|
||||
(click)="onClick($event, 'input', inputData[i].index);"
|
||||
/>
|
||||
</ng-container>
|
||||
<ng-container *ngFor="let output of outputs; let i = index">
|
||||
@ -70,6 +71,7 @@
|
||||
attr.marker-start="url(#{{output.class}}-arrow)"
|
||||
(pointerover)="onHover($event, 'output', i);"
|
||||
(pointerout)="onBlur($event, 'output', i);"
|
||||
(click)="onClick($event, 'output', outputData[i].index);"
|
||||
/>
|
||||
</ng-container>
|
||||
</svg>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Component, OnInit, Input, OnChanges, HostListener } from '@angular/core';
|
||||
import { Component, OnInit, Input, Output, EventEmitter, OnChanges, HostListener } from '@angular/core';
|
||||
import { Transaction } from '../../interfaces/electrs.interface';
|
||||
|
||||
interface SvgLine {
|
||||
@ -35,6 +35,9 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
||||
@Input() maxStrands = 24; // number of inputs/outputs to keep fully on-screen.
|
||||
@Input() tooltip = false;
|
||||
|
||||
@Output() selectInput = new EventEmitter<number>();
|
||||
@Output() selectOutput = new EventEmitter<number>();
|
||||
|
||||
inputData: Xput[];
|
||||
outputData: Xput[];
|
||||
inputs: SvgLine[];
|
||||
@ -76,11 +79,12 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
||||
this.combinedWeight = Math.min(this.maxCombinedWeight, Math.floor((this.width - (2 * this.midWidth)) / 6));
|
||||
|
||||
const totalValue = this.calcTotalValue(this.tx);
|
||||
let voutWithFee = this.tx.vout.map(v => {
|
||||
let voutWithFee = this.tx.vout.map((v, i) => {
|
||||
return {
|
||||
type: v.scriptpubkey_type === 'fee' ? 'fee' : 'output',
|
||||
value: v?.value,
|
||||
address: v?.scriptpubkey_address || v?.scriptpubkey_type?.toUpperCase(),
|
||||
index: i,
|
||||
pegout: v?.pegout?.scriptpubkey_address,
|
||||
confidential: (this.isLiquid && v?.value === undefined),
|
||||
} as Xput;
|
||||
@ -91,11 +95,12 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
||||
}
|
||||
const outputCount = voutWithFee.length;
|
||||
|
||||
let truncatedInputs = this.tx.vin.map(v => {
|
||||
let truncatedInputs = this.tx.vin.map((v, i) => {
|
||||
return {
|
||||
type: 'input',
|
||||
value: v?.prevout?.value,
|
||||
address: v?.prevout?.scriptpubkey_address || v?.prevout?.scriptpubkey_type?.toUpperCase(),
|
||||
index: i,
|
||||
coinbase: v?.is_coinbase,
|
||||
pegin: v?.is_pegin,
|
||||
confidential: (this.isLiquid && v?.prevout?.value === undefined),
|
||||
@ -306,8 +311,7 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
||||
};
|
||||
} else {
|
||||
this.hoverLine = {
|
||||
...this.outputData[index],
|
||||
index
|
||||
...this.outputData[index]
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -315,4 +319,12 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
||||
onBlur(event, side, index): void {
|
||||
this.hoverLine = null;
|
||||
}
|
||||
|
||||
onClick(event, side, index): void {
|
||||
if (side === 'input') {
|
||||
this.selectInput.emit(index);
|
||||
} else {
|
||||
this.selectOutput.emit(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user