diff --git a/frontend/src/app/components/transaction/transaction.component.html b/frontend/src/app/components/transaction/transaction.component.html
index 86930bcc7..ec0e824c8 100644
--- a/frontend/src/app/components/transaction/transaction.component.html
+++ b/frontend/src/app/components/transaction/transaction.component.html
@@ -210,8 +210,6 @@
[network]="network"
[tooltip]="true"
[inputIndex]="inputIndex" [outputIndex]="outputIndex"
- (selectInput)="selectInput($event)"
- (selectOutput)="selectOutput($event)"
>
diff --git a/frontend/src/app/components/transaction/transaction.component.ts b/frontend/src/app/components/transaction/transaction.component.ts
index 2be549569..0235dd887 100644
--- a/frontend/src/app/components/transaction/transaction.component.ts
+++ b/frontend/src/app/components/transaction/transaction.component.ts
@@ -18,6 +18,7 @@ import { ApiService } from '../../services/api.service';
import { SeoService } from '../../services/seo.service';
import { BlockExtended, CpfpInfo } from '../../interfaces/node-api.interface';
import { LiquidUnblinding } from './liquid-ublinding';
+import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
@Component({
selector: 'app-transaction',
@@ -40,6 +41,8 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
txReplacedSubscription: Subscription;
blocksSubscription: Subscription;
queryParamsSubscription: Subscription;
+ urlFragmentSubscription: Subscription;
+ fragmentParams: URLSearchParams;
rbfTransaction: undefined | Transaction;
cpfpInfo: CpfpInfo | null;
showCpfpDetails = false;
@@ -67,6 +70,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
constructor(
private route: ActivatedRoute,
private router: Router,
+ private relativeUrlPipe: RelativeUrlPipe,
private electrsApiService: ElectrsApiService,
private stateService: StateService,
private websocketService: WebsocketService,
@@ -93,6 +97,14 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
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$
.pipe(
switchMap((txId) =>
@@ -132,13 +144,29 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
switchMap((params: ParamMap) => {
const urlMatch = (params.get('id') || '').split(':');
if (urlMatch.length === 2 && urlMatch[1].length === 64) {
- this.inputIndex = parseInt(urlMatch[0], 10);
- this.outputIndex = null;
+ const vin = parseInt(urlMatch[0], 10);
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 {
this.txId = urlMatch[0];
- this.outputIndex = urlMatch[1] === undefined ? null : parseInt(urlMatch[1], 10);
- this.inputIndex = null;
+ const vout = parseInt(urlMatch[1], 10);
+ 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(
$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);
}
}
+ setTimeout(() => { this.applyFragment(); }, 0);
},
(error) => {
this.error = error;
@@ -359,14 +388,15 @@ 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;
+ // simulate normal anchor fragment behavior
+ applyFragment(): void {
+ const anchor = Array.from(this.fragmentParams.entries()).find(([frag, value]) => value === '');
+ if (anchor) {
+ const anchorElement = document.getElementById(anchor[0]);
+ if (anchorElement) {
+ anchorElement.scrollIntoView();
+ }
+ }
}
@HostListener('window:resize', ['$event'])
@@ -383,6 +413,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
this.blocksSubscription.unsubscribe();
this.queryParamsSubscription.unsubscribe();
this.flowPrefSubscription.unsubscribe();
+ this.urlFragmentSubscription.unsubscribe();
this.leaveTransaction();
}
}
diff --git a/frontend/src/app/components/transactions-list/transactions-list.component.html b/frontend/src/app/components/transactions-list/transactions-list.component.html
index e53c54a7a..a808eacd1 100644
--- a/frontend/src/app/components/transactions-list/transactions-list.component.html
+++ b/frontend/src/app/components/transactions-list/transactions-list.component.html
@@ -43,7 +43,7 @@
-
+
@@ -220,7 +220,7 @@
-
+
diff --git a/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.ts b/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.ts
index 9d29500f0..fa2faea8c 100644
--- a/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.ts
+++ b/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.ts
@@ -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 { Outspend, Transaction } from '../../interfaces/electrs.interface';
import { Router } from '@angular/router';
@@ -43,9 +43,6 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
@Input() inputIndex: number;
@Input() outputIndex: number;
- @Output() selectInput = new EventEmitter();
- @Output() selectOutput = new EventEmitter();
-
inputData: Xput[];
outputData: Xput[];
inputs: SvgLine[];
@@ -368,24 +365,42 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
onClick(event, side, index): void {
if (side === 'input') {
const input = this.tx.vin[index];
- if (input && input.txid && input.vout != null) {
- this.router.navigate([this.relativeUrlPipe.transform('/tx'), input.txid + ':' + input.vout], {
+ if (input && !input.is_coinbase && !input.is_pegin && input.txid && input.vout != null) {
+ this.router.navigate([this.relativeUrlPipe.transform('/tx'), input.txid], {
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 {
const output = this.tx.vout[index];
const outspend = this.outspends[index];
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',
- 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);
}
}
}
diff --git a/frontend/tsconfig.base.json b/frontend/tsconfig.base.json
index b12d6068c..c3676addb 100644
--- a/frontend/tsconfig.base.json
+++ b/frontend/tsconfig.base.json
@@ -16,7 +16,8 @@
],
"lib": [
"es2018",
- "dom"
+ "dom",
+ "dom.iterable"
]
},
"angularCompilerOptions": {