vin/vout selection syntax via url fragments
This commit is contained in:
parent
ee6766e34c
commit
ae9439a991
@ -210,8 +210,6 @@
|
|||||||
[network]="network"
|
[network]="network"
|
||||||
[tooltip]="true"
|
[tooltip]="true"
|
||||||
[inputIndex]="inputIndex" [outputIndex]="outputIndex"
|
[inputIndex]="inputIndex" [outputIndex]="outputIndex"
|
||||||
(selectInput)="selectInput($event)"
|
|
||||||
(selectOutput)="selectOutput($event)"
|
|
||||||
>
|
>
|
||||||
</tx-bowtie-graph>
|
</tx-bowtie-graph>
|
||||||
</div>
|
</div>
|
||||||
|
@ -18,6 +18,7 @@ import { ApiService } from '../../services/api.service';
|
|||||||
import { SeoService } from '../../services/seo.service';
|
import { SeoService } from '../../services/seo.service';
|
||||||
import { BlockExtended, CpfpInfo } from '../../interfaces/node-api.interface';
|
import { BlockExtended, CpfpInfo } from '../../interfaces/node-api.interface';
|
||||||
import { LiquidUnblinding } from './liquid-ublinding';
|
import { LiquidUnblinding } from './liquid-ublinding';
|
||||||
|
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-transaction',
|
selector: 'app-transaction',
|
||||||
@ -40,6 +41,8 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
txReplacedSubscription: Subscription;
|
txReplacedSubscription: Subscription;
|
||||||
blocksSubscription: Subscription;
|
blocksSubscription: Subscription;
|
||||||
queryParamsSubscription: Subscription;
|
queryParamsSubscription: Subscription;
|
||||||
|
urlFragmentSubscription: Subscription;
|
||||||
|
fragmentParams: URLSearchParams;
|
||||||
rbfTransaction: undefined | Transaction;
|
rbfTransaction: undefined | Transaction;
|
||||||
cpfpInfo: CpfpInfo | null;
|
cpfpInfo: CpfpInfo | null;
|
||||||
showCpfpDetails = false;
|
showCpfpDetails = false;
|
||||||
@ -67,6 +70,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
|
private relativeUrlPipe: RelativeUrlPipe,
|
||||||
private electrsApiService: ElectrsApiService,
|
private electrsApiService: ElectrsApiService,
|
||||||
private stateService: StateService,
|
private stateService: StateService,
|
||||||
private websocketService: WebsocketService,
|
private websocketService: WebsocketService,
|
||||||
@ -93,6 +97,14 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
map((da) => da.timeAvg)
|
map((da) => da.timeAvg)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.urlFragmentSubscription = this.route.fragment.subscribe((fragment) => {
|
||||||
|
this.fragmentParams = new URLSearchParams(fragment || '');
|
||||||
|
const vin = parseInt(this.fragmentParams.get('vin'), 10);
|
||||||
|
const vout = parseInt(this.fragmentParams.get('vout'), 10);
|
||||||
|
this.inputIndex = (!isNaN(vin) && vin >= 0) ? vin : null;
|
||||||
|
this.outputIndex = (!isNaN(vout) && vout >= 0) ? vout : null;
|
||||||
|
});
|
||||||
|
|
||||||
this.fetchCpfpSubscription = this.fetchCpfp$
|
this.fetchCpfpSubscription = this.fetchCpfp$
|
||||||
.pipe(
|
.pipe(
|
||||||
switchMap((txId) =>
|
switchMap((txId) =>
|
||||||
@ -132,13 +144,29 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
switchMap((params: ParamMap) => {
|
switchMap((params: ParamMap) => {
|
||||||
const urlMatch = (params.get('id') || '').split(':');
|
const urlMatch = (params.get('id') || '').split(':');
|
||||||
if (urlMatch.length === 2 && urlMatch[1].length === 64) {
|
if (urlMatch.length === 2 && urlMatch[1].length === 64) {
|
||||||
this.inputIndex = parseInt(urlMatch[0], 10);
|
const vin = parseInt(urlMatch[0], 10);
|
||||||
this.outputIndex = null;
|
|
||||||
this.txId = urlMatch[1];
|
this.txId = urlMatch[1];
|
||||||
|
// rewrite legacy vin syntax
|
||||||
|
if (!isNaN(vin)) {
|
||||||
|
this.fragmentParams.set('vin', vin.toString());
|
||||||
|
this.fragmentParams.delete('vout');
|
||||||
|
}
|
||||||
|
this.router.navigate([this.relativeUrlPipe.transform('/tx'), this.txId], {
|
||||||
|
queryParamsHandling: 'merge',
|
||||||
|
fragment: this.fragmentParams.toString(),
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
this.txId = urlMatch[0];
|
this.txId = urlMatch[0];
|
||||||
this.outputIndex = urlMatch[1] === undefined ? null : parseInt(urlMatch[1], 10);
|
const vout = parseInt(urlMatch[1], 10);
|
||||||
this.inputIndex = null;
|
if (urlMatch.length > 1 && !isNaN(vout)) {
|
||||||
|
// rewrite legacy vout syntax
|
||||||
|
this.fragmentParams.set('vout', vout.toString());
|
||||||
|
this.fragmentParams.delete('vin');
|
||||||
|
this.router.navigate([this.relativeUrlPipe.transform('/tx'), this.txId], {
|
||||||
|
queryParamsHandling: 'merge',
|
||||||
|
fragment: this.fragmentParams.toString(),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.seoService.setTitle(
|
this.seoService.setTitle(
|
||||||
$localize`:@@bisq.transaction.browser-title:Transaction: ${this.txId}:INTERPOLATION:`
|
$localize`:@@bisq.transaction.browser-title:Transaction: ${this.txId}:INTERPOLATION:`
|
||||||
@ -222,6 +250,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
this.fetchCpfp$.next(this.tx.txid);
|
this.fetchCpfp$.next(this.tx.txid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
setTimeout(() => { this.applyFragment(); }, 0);
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
this.error = error;
|
this.error = error;
|
||||||
@ -359,14 +388,15 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
this.graphExpanded = false;
|
this.graphExpanded = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
selectInput(input) {
|
// simulate normal anchor fragment behavior
|
||||||
this.inputIndex = input;
|
applyFragment(): void {
|
||||||
this.outputIndex = null;
|
const anchor = Array.from(this.fragmentParams.entries()).find(([frag, value]) => value === '');
|
||||||
}
|
if (anchor) {
|
||||||
|
const anchorElement = document.getElementById(anchor[0]);
|
||||||
selectOutput(output) {
|
if (anchorElement) {
|
||||||
this.outputIndex = output;
|
anchorElement.scrollIntoView();
|
||||||
this.inputIndex = null;
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('window:resize', ['$event'])
|
@HostListener('window:resize', ['$event'])
|
||||||
@ -383,6 +413,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
this.blocksSubscription.unsubscribe();
|
this.blocksSubscription.unsubscribe();
|
||||||
this.queryParamsSubscription.unsubscribe();
|
this.queryParamsSubscription.unsubscribe();
|
||||||
this.flowPrefSubscription.unsubscribe();
|
this.flowPrefSubscription.unsubscribe();
|
||||||
|
this.urlFragmentSubscription.unsubscribe();
|
||||||
this.leaveTransaction();
|
this.leaveTransaction();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template #defaultPrevout>
|
<ng-template #defaultPrevout>
|
||||||
<a [routerLink]="['/tx/' | relativeUrl, vin.txid + ':' + vin.vout]" class="red">
|
<a [routerLink]="['/tx/' | relativeUrl, vin.txid]" [fragment]="'vout=' + vin.vout" class="red">
|
||||||
<fa-icon [icon]="['fas', 'arrow-alt-circle-right']" [fixedWidth]="true"></fa-icon>
|
<fa-icon [icon]="['fas', 'arrow-alt-circle-right']" [fixedWidth]="true"></fa-icon>
|
||||||
</a>
|
</a>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
@ -220,7 +220,7 @@
|
|||||||
<fa-icon [icon]="['fas', 'arrow-alt-circle-right']" [fixedWidth]="true"></fa-icon>
|
<fa-icon [icon]="['fas', 'arrow-alt-circle-right']" [fixedWidth]="true"></fa-icon>
|
||||||
</span>
|
</span>
|
||||||
<ng-template #spent>
|
<ng-template #spent>
|
||||||
<a *ngIf="tx._outspends[vindex].txid else outputNoTxId" [routerLink]="['/tx/' | relativeUrl, tx._outspends[vindex].vin + ':' + tx._outspends[vindex].txid]" class="red">
|
<a *ngIf="tx._outspends[vindex].txid else outputNoTxId" [routerLink]="['/tx/' | relativeUrl, tx._outspends[vindex].txid]" [fragment]="'vin=' + tx._outspends[vindex].vin" class="red">
|
||||||
<fa-icon [icon]="['fas', 'arrow-alt-circle-right']" [fixedWidth]="true"></fa-icon>
|
<fa-icon [icon]="['fas', 'arrow-alt-circle-right']" [fixedWidth]="true"></fa-icon>
|
||||||
</a>
|
</a>
|
||||||
<ng-template #outputNoTxId>
|
<ng-template #outputNoTxId>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Component, OnInit, Input, Output, EventEmitter, OnChanges, HostListener } from '@angular/core';
|
import { Component, OnInit, Input, OnChanges, HostListener } from '@angular/core';
|
||||||
import { StateService } from '../../services/state.service';
|
import { StateService } from '../../services/state.service';
|
||||||
import { Outspend, Transaction } from '../../interfaces/electrs.interface';
|
import { Outspend, Transaction } from '../../interfaces/electrs.interface';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
@ -43,9 +43,6 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
|||||||
@Input() inputIndex: number;
|
@Input() inputIndex: number;
|
||||||
@Input() outputIndex: number;
|
@Input() outputIndex: number;
|
||||||
|
|
||||||
@Output() selectInput = new EventEmitter<number>();
|
|
||||||
@Output() selectOutput = new EventEmitter<number>();
|
|
||||||
|
|
||||||
inputData: Xput[];
|
inputData: Xput[];
|
||||||
outputData: Xput[];
|
outputData: Xput[];
|
||||||
inputs: SvgLine[];
|
inputs: SvgLine[];
|
||||||
@ -369,23 +366,41 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
|||||||
if (side === 'input') {
|
if (side === 'input') {
|
||||||
const input = this.tx.vin[index];
|
const input = this.tx.vin[index];
|
||||||
if (input && input.txid && input.vout != null) {
|
if (input && input.txid && input.vout != null) {
|
||||||
this.router.navigate([this.relativeUrlPipe.transform('/tx'), input.txid + ':' + input.vout], {
|
this.router.navigate([this.relativeUrlPipe.transform('/tx'), input.txid], {
|
||||||
queryParamsHandling: 'merge',
|
queryParamsHandling: 'merge',
|
||||||
fragment: 'flow'
|
fragment: (new URLSearchParams({
|
||||||
|
flow: '',
|
||||||
|
vout: input.vout.toString(),
|
||||||
|
})).toString(),
|
||||||
|
});
|
||||||
|
} else if (index != null) {
|
||||||
|
this.router.navigate([this.relativeUrlPipe.transform('/tx'), this.tx.txid], {
|
||||||
|
queryParamsHandling: 'merge',
|
||||||
|
fragment: (new URLSearchParams({
|
||||||
|
flow: '',
|
||||||
|
vin: index.toString(),
|
||||||
|
})).toString(),
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
this.selectInput.emit(index);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const output = this.tx.vout[index];
|
const output = this.tx.vout[index];
|
||||||
const outspend = this.outspends[index];
|
const outspend = this.outspends[index];
|
||||||
if (output && outspend && outspend.spent && outspend.txid) {
|
if (output && outspend && outspend.spent && outspend.txid) {
|
||||||
this.router.navigate([this.relativeUrlPipe.transform('/tx'), outspend.vin + ':' + outspend.txid], {
|
this.router.navigate([this.relativeUrlPipe.transform('/tx'), outspend.txid], {
|
||||||
queryParamsHandling: 'merge',
|
queryParamsHandling: 'merge',
|
||||||
fragment: 'flow'
|
fragment: (new URLSearchParams({
|
||||||
|
flow: '',
|
||||||
|
vin: outspend.vin.toString(),
|
||||||
|
})).toString(),
|
||||||
|
});
|
||||||
|
} else if (index != null) {
|
||||||
|
this.router.navigate([this.relativeUrlPipe.transform('/tx'), this.tx.txid], {
|
||||||
|
queryParamsHandling: 'merge',
|
||||||
|
fragment: (new URLSearchParams({
|
||||||
|
flow: '',
|
||||||
|
vout: index.toString(),
|
||||||
|
})).toString(),
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
this.selectOutput.emit(index);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,8 @@
|
|||||||
],
|
],
|
||||||
"lib": [
|
"lib": [
|
||||||
"es2018",
|
"es2018",
|
||||||
"dom"
|
"dom",
|
||||||
|
"dom.iterable"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"angularCompilerOptions": {
|
"angularCompilerOptions": {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user