Merge branch 'master' into detailed-unfurler-logs
This commit is contained in:
commit
43d56a2121
@ -25,6 +25,8 @@ export class AppComponent implements OnInit {
|
|||||||
if (this.locale.startsWith('ar') || this.locale.startsWith('fa') || this.locale.startsWith('he')) {
|
if (this.locale.startsWith('ar') || this.locale.startsWith('fa') || this.locale.startsWith('he')) {
|
||||||
this.dir = 'rtl';
|
this.dir = 'rtl';
|
||||||
this.class = 'rtl-layout';
|
this.class = 'rtl-layout';
|
||||||
|
} else {
|
||||||
|
this.class = 'ltr-layout';
|
||||||
}
|
}
|
||||||
|
|
||||||
tooltipConfig.animation = false;
|
tooltipConfig.animation = false;
|
||||||
|
@ -27,7 +27,6 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
top: 75px;
|
top: 75px;
|
||||||
transform: translateX(50vw);
|
transform: translateX(50vw);
|
||||||
transition: transform 1s;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.position-container.liquid, .position-container.liquidtestnet {
|
.position-container.liquid, .position-container.liquidtestnet {
|
||||||
@ -84,9 +83,9 @@
|
|||||||
|
|
||||||
.time-toggle {
|
.time-toggle {
|
||||||
color: white;
|
color: white;
|
||||||
font-size: 1rem;
|
font-size: 0.8rem;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: -1.5em;
|
bottom: -1.8em;
|
||||||
left: 1px;
|
left: 1px;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
background: none;
|
background: none;
|
||||||
@ -97,14 +96,31 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.blockchain-wrapper.ltr-transition .blocks-wrapper,
|
.blockchain-wrapper.ltr-transition .blocks-wrapper,
|
||||||
|
.blockchain-wrapper.ltr-transition .position-container,
|
||||||
.blockchain-wrapper.ltr-transition .time-toggle {
|
.blockchain-wrapper.ltr-transition .time-toggle {
|
||||||
transition: transform 1s;
|
transition: transform 1s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.blockchain-wrapper.time-ltr .blocks-wrapper {
|
.blockchain-wrapper.time-ltr {
|
||||||
transform: scaleX(-1);
|
.blocks-wrapper {
|
||||||
|
transform: scaleX(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-toggle {
|
||||||
|
transform: translateX(-50%) scaleX(-1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.blockchain-wrapper.time-ltr .time-toggle {
|
:host-context(.ltr-layout) {
|
||||||
transform: translateX(-50%) scaleX(-1);
|
.blockchain-wrapper.time-ltr .blocks-wrapper,
|
||||||
|
.blockchain-wrapper .blocks-wrapper {
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:host-context(.rtl-layout) {
|
||||||
|
.blockchain-wrapper.time-ltr .blocks-wrapper,
|
||||||
|
.blockchain-wrapper .blocks-wrapper {
|
||||||
|
direction: rtl;
|
||||||
|
}
|
||||||
}
|
}
|
@ -146,4 +146,10 @@
|
|||||||
.block-body {
|
.block-body {
|
||||||
transform: scaleX(-1);
|
transform: scaleX(-1);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:host-context(.rtl-layout) {
|
||||||
|
#arrow-up {
|
||||||
|
transform: translateX(70px);
|
||||||
|
}
|
||||||
}
|
}
|
@ -287,11 +287,12 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
this.arrowVisible = true;
|
this.arrowVisible = true;
|
||||||
|
|
||||||
for (const block of this.mempoolBlocks) {
|
let found = false;
|
||||||
for (let i = 0; i < block.feeRange.length - 1; i++) {
|
for (let txInBlockIndex = 0; txInBlockIndex < this.mempoolBlocks.length && !found; txInBlockIndex++) {
|
||||||
|
const block = this.mempoolBlocks[txInBlockIndex];
|
||||||
|
for (let i = 0; i < block.feeRange.length - 1 && !found; i++) {
|
||||||
if (this.txFeePerVSize < block.feeRange[i + 1] && this.txFeePerVSize >= block.feeRange[i]) {
|
if (this.txFeePerVSize < block.feeRange[i + 1] && this.txFeePerVSize >= block.feeRange[i]) {
|
||||||
const txInBlockIndex = this.mempoolBlocks.indexOf(block);
|
const feeRangeIndex = i;
|
||||||
const feeRangeIndex = block.feeRange.findIndex((val, index) => this.txFeePerVSize < block.feeRange[index + 1]);
|
|
||||||
const feeRangeChunkSize = 1 / (block.feeRange.length - 1);
|
const feeRangeChunkSize = 1 / (block.feeRange.length - 1);
|
||||||
|
|
||||||
const txFee = this.txFeePerVSize - block.feeRange[i];
|
const txFee = this.txFeePerVSize - block.feeRange[i];
|
||||||
@ -306,9 +307,13 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
|
|||||||
+ ((1 - feePosition) * blockedFilledPercentage * this.blockWidth);
|
+ ((1 - feePosition) * blockedFilledPercentage * this.blockWidth);
|
||||||
|
|
||||||
this.rightPosition = arrowRightPosition;
|
this.rightPosition = arrowRightPosition;
|
||||||
break;
|
found = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (this.txFeePerVSize >= block.feeRange[block.feeRange.length - 1]) {
|
||||||
|
this.rightPosition = txInBlockIndex * (this.blockWidth + this.blockPadding);
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
<div *ngIf="countdown > 0" class="warning-label">{{ eventName }} in {{ countdown | number }} block{{ countdown === 1 ? '' : 's' }}!</div>
|
<div *ngIf="countdown > 0" class="warning-label">{{ eventName }} in {{ countdown | number }} block{{ countdown === 1 ? '' : 's' }}!</div>
|
||||||
|
|
||||||
<div id="blockchain-container" dir="ltr" #blockchainContainer
|
<div id="blockchain-container" [dir]="timeLtr ? 'rtl' : 'ltr'" #blockchainContainer
|
||||||
(mousedown)="onMouseDown($event)"
|
(mousedown)="onMouseDown($event)"
|
||||||
(dragstart)="onDragStart($event)"
|
(dragstart)="onDragStart($event)"
|
||||||
>
|
>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Component, ElementRef, HostListener, OnInit, ViewChild } from '@angular/core';
|
import { Component, ElementRef, HostListener, OnInit, OnDestroy, ViewChild } from '@angular/core';
|
||||||
|
import { Subscription } from 'rxjs';
|
||||||
import { StateService } from '../../services/state.service';
|
import { StateService } from '../../services/state.service';
|
||||||
import { specialBlocks } from '../../app.constants';
|
import { specialBlocks } from '../../app.constants';
|
||||||
|
|
||||||
@ -7,7 +8,7 @@ import { specialBlocks } from '../../app.constants';
|
|||||||
templateUrl: './start.component.html',
|
templateUrl: './start.component.html',
|
||||||
styleUrls: ['./start.component.scss'],
|
styleUrls: ['./start.component.scss'],
|
||||||
})
|
})
|
||||||
export class StartComponent implements OnInit {
|
export class StartComponent implements OnInit, OnDestroy {
|
||||||
interval = 60;
|
interval = 60;
|
||||||
colors = ['#5E35B1', '#ffffff'];
|
colors = ['#5E35B1', '#ffffff'];
|
||||||
|
|
||||||
@ -16,6 +17,8 @@ export class StartComponent implements OnInit {
|
|||||||
eventName = '';
|
eventName = '';
|
||||||
mouseDragStartX: number;
|
mouseDragStartX: number;
|
||||||
blockchainScrollLeftInit: number;
|
blockchainScrollLeftInit: number;
|
||||||
|
timeLtrSubscription: Subscription;
|
||||||
|
timeLtr: boolean = this.stateService.timeLtr.value;
|
||||||
@ViewChild('blockchainContainer') blockchainContainer: ElementRef;
|
@ViewChild('blockchainContainer') blockchainContainer: ElementRef;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -23,6 +26,9 @@ export class StartComponent implements OnInit {
|
|||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
this.timeLtrSubscription = this.stateService.timeLtr.subscribe((ltr) => {
|
||||||
|
this.timeLtr = !!ltr;
|
||||||
|
});
|
||||||
this.stateService.blocks$
|
this.stateService.blocks$
|
||||||
.subscribe((blocks: any) => {
|
.subscribe((blocks: any) => {
|
||||||
if (this.stateService.network !== '') {
|
if (this.stateService.network !== '') {
|
||||||
@ -72,4 +78,8 @@ export class StartComponent implements OnInit {
|
|||||||
this.mouseDragStartX = null;
|
this.mouseDragStartX = null;
|
||||||
this.stateService.setBlockScrollingInProgress(false);
|
this.stateService.setBlockScrollingInProgress(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.timeLtrSubscription.unsubscribe();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -195,7 +195,7 @@
|
|||||||
<h2 id="flow" i18n="transaction.flow|Transaction flow">Flow</h2>
|
<h2 id="flow" i18n="transaction.flow|Transaction flow">Flow</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="button" class="btn btn-outline-info flow-toggle btn-sm float-right" (click)="toggleGraph()" i18n="hide-flow-diagram">Hide flow diagram</button>
|
<button type="button" class="btn btn-outline-info flow-toggle btn-sm float-right" (click)="toggleGraph()" i18n="hide-diagram">Hide diagram</button>
|
||||||
|
|
||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
|
|
||||||
@ -208,7 +208,11 @@
|
|||||||
[lineLimit]="inOutLimit"
|
[lineLimit]="inOutLimit"
|
||||||
[maxStrands]="graphExpanded ? maxInOut : 24"
|
[maxStrands]="graphExpanded ? maxInOut : 24"
|
||||||
[network]="network"
|
[network]="network"
|
||||||
[tooltip]="true">
|
[tooltip]="true"
|
||||||
|
[inputIndex]="inputIndex" [outputIndex]="outputIndex"
|
||||||
|
(selectInput)="selectInput($event)"
|
||||||
|
(selectOutput)="selectOutput($event)"
|
||||||
|
>
|
||||||
</tx-bowtie-graph>
|
</tx-bowtie-graph>
|
||||||
</div>
|
</div>
|
||||||
<div class="toggle-wrapper" *ngIf="maxInOut > 24">
|
<div class="toggle-wrapper" *ngIf="maxInOut > 24">
|
||||||
@ -234,13 +238,13 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="title-buttons">
|
<div class="title-buttons">
|
||||||
<button *ngIf="!showFlow" type="button" class="btn btn-outline-info flow-toggle btn-sm" (click)="toggleGraph()" i18n="show">Show flow diagram</button>
|
<button *ngIf="!showFlow" type="button" class="btn btn-outline-info flow-toggle btn-sm" (click)="toggleGraph()" i18n="show-diagram">Show diagram</button>
|
||||||
<button type="button" class="btn btn-outline-info btn-sm" (click)="txList.toggleDetails()" i18n="transaction.details|Transaction Details">Details</button>
|
<button type="button" class="btn btn-outline-info btn-sm" (click)="txList.toggleDetails()" i18n="transaction.details|Transaction Details">Details</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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">
|
<div class="title text-left">
|
||||||
<h2 i18n="transaction.details">Details</h2>
|
<h2 i18n="transaction.details">Details</h2>
|
||||||
|
@ -3,38 +3,38 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.container-buttons {
|
.container-buttons {
|
||||||
align-self: center;
|
align-self: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-block {
|
.title-block {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
align-items: baseline;
|
||||||
@media (min-width: 650px) {
|
@media (min-width: 650px) {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
h1 {
|
h1 {
|
||||||
margin: 0rem;
|
margin: 0rem;
|
||||||
|
margin-right: 15px;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.tx-link {
|
.tx-link {
|
||||||
display: flex;
|
|
||||||
flex-grow: 1;
|
|
||||||
margin-bottom: 0px;
|
margin-bottom: 0px;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
@media (min-width: 650px) {
|
display: inline-block;
|
||||||
align-self: end;
|
width: 100%;
|
||||||
margin-left: 15px;
|
flex-shrink: 0;
|
||||||
margin-top: 0px;
|
@media (min-width: 651px) {
|
||||||
margin-bottom: -3px;
|
display: flex;
|
||||||
}
|
width: auto;
|
||||||
@media (min-width: 768px) {
|
flex-grow: 1;
|
||||||
margin-bottom: 0px;
|
margin-bottom: 0px;
|
||||||
top: 1px;
|
top: 1px;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 650px) {
|
||||||
order: 3;
|
order: 3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.td-width {
|
.td-width {
|
||||||
|
@ -47,6 +47,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
now = new Date().getTime();
|
now = new Date().getTime();
|
||||||
timeAvg$: Observable<number>;
|
timeAvg$: Observable<number>;
|
||||||
liquidUnblinding = new LiquidUnblinding();
|
liquidUnblinding = new LiquidUnblinding();
|
||||||
|
inputIndex: number;
|
||||||
outputIndex: number;
|
outputIndex: number;
|
||||||
showFlow: boolean = true;
|
showFlow: boolean = true;
|
||||||
graphExpanded: boolean = false;
|
graphExpanded: boolean = false;
|
||||||
@ -121,8 +122,15 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
.pipe(
|
.pipe(
|
||||||
switchMap((params: ParamMap) => {
|
switchMap((params: ParamMap) => {
|
||||||
const urlMatch = (params.get('id') || '').split(':');
|
const urlMatch = (params.get('id') || '').split(':');
|
||||||
this.txId = urlMatch[0];
|
if (urlMatch.length === 2 && urlMatch[1].length === 64) {
|
||||||
this.outputIndex = urlMatch[1] === undefined ? null : parseInt(urlMatch[1], 10);
|
this.inputIndex = parseInt(urlMatch[0], 10);
|
||||||
|
this.outputIndex = null;
|
||||||
|
this.txId = urlMatch[1];
|
||||||
|
} else {
|
||||||
|
this.txId = urlMatch[0];
|
||||||
|
this.outputIndex = urlMatch[1] === undefined ? null : parseInt(urlMatch[1], 10);
|
||||||
|
this.inputIndex = null;
|
||||||
|
}
|
||||||
this.seoService.setTitle(
|
this.seoService.setTitle(
|
||||||
$localize`:@@bisq.transaction.browser-title:Transaction: ${this.txId}:INTERPOLATION:`
|
$localize`:@@bisq.transaction.browser-title:Transaction: ${this.txId}:INTERPOLATION:`
|
||||||
);
|
);
|
||||||
@ -334,6 +342,16 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
this.graphExpanded = false;
|
this.graphExpanded = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
selectInput(input) {
|
||||||
|
this.inputIndex = input;
|
||||||
|
this.outputIndex = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectOutput(output) {
|
||||||
|
this.outputIndex = output;
|
||||||
|
this.inputIndex = null;
|
||||||
|
}
|
||||||
|
|
||||||
@HostListener('window:resize', ['$event'])
|
@HostListener('window:resize', ['$event'])
|
||||||
setGraphSize(): void {
|
setGraphSize(): void {
|
||||||
if (this.graphContainer) {
|
if (this.graphContainer) {
|
||||||
|
@ -20,9 +20,9 @@
|
|||||||
<div class="col">
|
<div class="col">
|
||||||
<table class="table table-borderless smaller-text table-sm table-tx-vin">
|
<table class="table table-borderless smaller-text table-sm table-tx-vin">
|
||||||
<tbody>
|
<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]="{
|
<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 !== ''
|
'highlight': vin.prevout?.scriptpubkey_address === this.address && this.address !== ''
|
||||||
}">
|
}">
|
||||||
<td class="arrow-td">
|
<td class="arrow-td">
|
||||||
@ -146,7 +146,7 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<tr *ngIf="tx.vin.length > rowLimit && tx['@vinLimit']">
|
<tr *ngIf="tx.vin.length > inputRowLimit && tx['@vinLimit']">
|
||||||
<td colspan="3" class="text-center">
|
<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>
|
<button class="btn btn-sm btn-primary mt-2" (click)="loadMoreInputs(tx);"><span i18n="show-all">Show all</span> ({{ tx.vin.length }})</button>
|
||||||
</td>
|
</td>
|
||||||
@ -158,7 +158,7 @@
|
|||||||
<div class="col mobile-bottomcol">
|
<div class="col mobile-bottomcol">
|
||||||
<table class="table table-borderless smaller-text table-sm table-tx-vout">
|
<table class="table table-borderless smaller-text table-sm table-tx-vout">
|
||||||
<tbody>
|
<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]="{
|
<tr [ngClass]="{
|
||||||
'assetBox': assetsMinimal && assetsMinimal[vout.asset] && vout.scriptpubkey_address && tx.vin && !tx.vin[0].is_coinbase && tx._unblinded || outputIndex === vindex,
|
'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 !== ''
|
'highlight': vout.scriptpubkey_address === this.address && this.address !== ''
|
||||||
@ -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].txid]" class="red">
|
<a *ngIf="tx._outspends[vindex].txid else outputNoTxId" [routerLink]="['/tx/' | relativeUrl, tx._outspends[vindex].vin + ':' + tx._outspends[vindex].txid]" 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>
|
||||||
@ -257,7 +257,7 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</ng-template>
|
</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">
|
<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>
|
<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>
|
</td>
|
||||||
|
@ -24,6 +24,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
|||||||
@Input() transactionPage = false;
|
@Input() transactionPage = false;
|
||||||
@Input() errorUnblinded = false;
|
@Input() errorUnblinded = false;
|
||||||
@Input() paginated = false;
|
@Input() paginated = false;
|
||||||
|
@Input() inputIndex: number;
|
||||||
@Input() outputIndex: number;
|
@Input() outputIndex: number;
|
||||||
@Input() address: string = '';
|
@Input() address: string = '';
|
||||||
@Input() rowLimit = 12;
|
@Input() rowLimit = 12;
|
||||||
@ -37,6 +38,8 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
|||||||
showDetails$ = new BehaviorSubject<boolean>(false);
|
showDetails$ = new BehaviorSubject<boolean>(false);
|
||||||
assetsMinimal: any;
|
assetsMinimal: any;
|
||||||
transactionsLength: number = 0;
|
transactionsLength: number = 0;
|
||||||
|
inputRowLimit: number = 12;
|
||||||
|
outputRowLimit: number = 12;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public stateService: StateService,
|
public stateService: StateService,
|
||||||
@ -97,50 +100,57 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
|||||||
).subscribe(() => this.ref.markForCheck());
|
).subscribe(() => this.ref.markForCheck());
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges(): void {
|
ngOnChanges(changes): void {
|
||||||
if (!this.transactions || !this.transactions.length) {
|
if (changes.inputIndex || changes.outputIndex || changes.rowLimit) {
|
||||||
return;
|
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) && !changes.transactions) {
|
||||||
|
setTimeout(() => {
|
||||||
|
const assetBoxElements = document.getElementsByClassName('assetBox');
|
||||||
|
if (assetBoxElements && assetBoxElements[0]) {
|
||||||
|
assetBoxElements[0].scrollIntoView({block: "center"});
|
||||||
|
}
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (changes.transactions || changes.address) {
|
||||||
this.transactionsLength = this.transactions.length;
|
if (!this.transactions || !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) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.address) {
|
this.transactionsLength = this.transactions.length;
|
||||||
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;
|
this.transactions.forEach((tx) => {
|
||||||
}
|
tx['@voutLimit'] = true;
|
||||||
});
|
tx['@vinLimit'] = true;
|
||||||
const txIds = this.transactions.filter((tx) => !tx._outspends).map((tx) => tx.txid);
|
if (tx['addressValue'] !== undefined) {
|
||||||
if (txIds.length) {
|
return;
|
||||||
this.refreshOutspends$.next(txIds);
|
}
|
||||||
}
|
|
||||||
if (this.stateService.env.LIGHTNING) {
|
if (this.address) {
|
||||||
const txIds = this.transactions.filter((tx) => !tx._channels).map((tx) => tx.txid);
|
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) {
|
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="'output'" i18n="transaction.output">Output</span>
|
||||||
<span *ngSwitchCase="'fee'" i18n="transaction.fee">Fee</span>
|
<span *ngSwitchCase="'fee'" i18n="transaction.fee">Fee</span>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<span *ngIf="line.type !== 'fee'"> #{{ line.index }}</span>
|
<span *ngIf="line.type !== 'fee'"> #{{ line.index + 1 }}</span>
|
||||||
</p>
|
</p>
|
||||||
<p *ngIf="line.value == null && line.confidential" i18n="shared.confidential">Confidential</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>
|
<p *ngIf="line.value != null"><app-amount [satoshis]="line.value"></app-amount></p>
|
||||||
|
@ -41,6 +41,18 @@
|
|||||||
<stop offset="98%" [attr.stop-color]="gradient[0]" />
|
<stop offset="98%" [attr.stop-color]="gradient[0]" />
|
||||||
<stop offset="100%" [attr.stop-color]="gradient[0]" />
|
<stop offset="100%" [attr.stop-color]="gradient[0]" />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
|
<linearGradient id="input-highlight-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||||
|
<stop offset="0%" [attr.stop-color]="gradient[0]" />
|
||||||
|
<stop offset="2%" [attr.stop-color]="gradient[0]" />
|
||||||
|
<stop offset="30%" stop-color="#1bd8f4" />
|
||||||
|
<stop offset="100%" [attr.stop-color]="gradient[1]" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="output-highlight-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||||
|
<stop offset="0%" [attr.stop-color]="gradient[1]" />
|
||||||
|
<stop offset="70%" stop-color="#1bd8f4" />
|
||||||
|
<stop offset="98%" [attr.stop-color]="gradient[0]" />
|
||||||
|
<stop offset="100%" [attr.stop-color]="gradient[0]" />
|
||||||
|
</linearGradient>
|
||||||
<linearGradient id="fee-hover-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
<linearGradient id="fee-hover-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||||
<stop offset="0%" [attr.stop-color]="gradient[1]" />
|
<stop offset="0%" [attr.stop-color]="gradient[1]" />
|
||||||
<stop offset="100%" stop-color="white" />
|
<stop offset="100%" stop-color="white" />
|
||||||
@ -56,20 +68,24 @@
|
|||||||
<path
|
<path
|
||||||
[attr.d]="input.path"
|
[attr.d]="input.path"
|
||||||
class="line {{input.class}}"
|
class="line {{input.class}}"
|
||||||
|
[class.highlight]="inputData[i].index === inputIndex"
|
||||||
[style]="input.style"
|
[style]="input.style"
|
||||||
attr.marker-start="url(#{{input.class}}-arrow)"
|
attr.marker-start="url(#{{input.class}}-arrow)"
|
||||||
(pointerover)="onHover($event, 'input', i);"
|
(pointerover)="onHover($event, 'input', i);"
|
||||||
(pointerout)="onBlur($event, 'input', i);"
|
(pointerout)="onBlur($event, 'input', i);"
|
||||||
|
(click)="onClick($event, 'input', inputData[i].index);"
|
||||||
/>
|
/>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngFor="let output of outputs; let i = index">
|
<ng-container *ngFor="let output of outputs; let i = index">
|
||||||
<path
|
<path
|
||||||
[attr.d]="output.path"
|
[attr.d]="output.path"
|
||||||
class="line {{output.class}}"
|
class="line {{output.class}}"
|
||||||
|
[class.highlight]="outputData[i].index === outputIndex"
|
||||||
[style]="output.style"
|
[style]="output.style"
|
||||||
attr.marker-start="url(#{{output.class}}-arrow)"
|
attr.marker-start="url(#{{output.class}}-arrow)"
|
||||||
(pointerover)="onHover($event, 'output', i);"
|
(pointerover)="onHover($event, 'output', i);"
|
||||||
(pointerout)="onBlur($event, 'output', i);"
|
(pointerout)="onBlur($event, 'output', i);"
|
||||||
|
(click)="onClick($event, 'output', outputData[i].index);"
|
||||||
/>
|
/>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</svg>
|
</svg>
|
||||||
|
@ -12,6 +12,17 @@
|
|||||||
stroke: url(#fee-gradient);
|
stroke: url(#fee-gradient);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.highlight {
|
||||||
|
z-index: 8;
|
||||||
|
cursor: pointer;
|
||||||
|
&.input {
|
||||||
|
stroke: url(#input-highlight-gradient);
|
||||||
|
}
|
||||||
|
&.output {
|
||||||
|
stroke: url(#output-highlight-gradient);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
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';
|
import { StateService } from '../../services/state.service';
|
||||||
|
import { Outspend, Transaction } from '../../interfaces/electrs.interface';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { ReplaySubject, merge, Subscription } from 'rxjs';
|
||||||
|
import { tap, switchMap } from 'rxjs/operators';
|
||||||
|
import { ApiService } from '../../services/api.service';
|
||||||
|
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
|
||||||
|
|
||||||
interface SvgLine {
|
interface SvgLine {
|
||||||
path: string;
|
path: string;
|
||||||
@ -34,6 +40,11 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
|||||||
@Input() minWeight = 2; //
|
@Input() minWeight = 2; //
|
||||||
@Input() maxStrands = 24; // number of inputs/outputs to keep fully on-screen.
|
@Input() maxStrands = 24; // number of inputs/outputs to keep fully on-screen.
|
||||||
@Input() tooltip = false;
|
@Input() tooltip = false;
|
||||||
|
@Input() inputIndex: number;
|
||||||
|
@Input() outputIndex: number;
|
||||||
|
|
||||||
|
@Output() selectInput = new EventEmitter<number>();
|
||||||
|
@Output() selectOutput = new EventEmitter<number>();
|
||||||
|
|
||||||
inputData: Xput[];
|
inputData: Xput[];
|
||||||
outputData: Xput[];
|
outputData: Xput[];
|
||||||
@ -45,6 +56,10 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
|||||||
isLiquid: boolean = false;
|
isLiquid: boolean = false;
|
||||||
hoverLine: Xput | void = null;
|
hoverLine: Xput | void = null;
|
||||||
tooltipPosition = { x: 0, y: 0 };
|
tooltipPosition = { x: 0, y: 0 };
|
||||||
|
outspends: Outspend[] = [];
|
||||||
|
|
||||||
|
outspendsSubscription: Subscription;
|
||||||
|
refreshOutspends$: ReplaySubject<string> = new ReplaySubject();
|
||||||
|
|
||||||
gradientColors = {
|
gradientColors = {
|
||||||
'': ['#9339f4', '#105fb0'],
|
'': ['#9339f4', '#105fb0'],
|
||||||
@ -61,12 +76,45 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
|||||||
|
|
||||||
gradient: string[] = ['#105fb0', '#105fb0'];
|
gradient: string[] = ['#105fb0', '#105fb0'];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private router: Router,
|
||||||
|
private relativeUrlPipe: RelativeUrlPipe,
|
||||||
|
private stateService: StateService,
|
||||||
|
private apiService: ApiService,
|
||||||
|
) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.initGraph();
|
this.initGraph();
|
||||||
|
|
||||||
|
this.outspendsSubscription = merge(
|
||||||
|
this.refreshOutspends$
|
||||||
|
.pipe(
|
||||||
|
switchMap((txid) => this.apiService.getOutspendsBatched$([txid])),
|
||||||
|
tap((outspends: Outspend[][]) => {
|
||||||
|
if (!this.tx || !outspends || !outspends.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.outspends = outspends[0];
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
this.stateService.utxoSpent$
|
||||||
|
.pipe(
|
||||||
|
tap((utxoSpent) => {
|
||||||
|
for (const i in utxoSpent) {
|
||||||
|
this.outspends[i] = {
|
||||||
|
spent: true,
|
||||||
|
txid: utxoSpent[i].txid,
|
||||||
|
vin: utxoSpent[i].vin,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
).subscribe(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges(): void {
|
ngOnChanges(): void {
|
||||||
this.initGraph();
|
this.initGraph();
|
||||||
|
this.refreshOutspends$.next(this.tx.txid);
|
||||||
}
|
}
|
||||||
|
|
||||||
initGraph(): void {
|
initGraph(): void {
|
||||||
@ -76,11 +124,12 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
|||||||
this.combinedWeight = Math.min(this.maxCombinedWeight, Math.floor((this.width - (2 * this.midWidth)) / 6));
|
this.combinedWeight = Math.min(this.maxCombinedWeight, Math.floor((this.width - (2 * this.midWidth)) / 6));
|
||||||
|
|
||||||
const totalValue = this.calcTotalValue(this.tx);
|
const totalValue = this.calcTotalValue(this.tx);
|
||||||
let voutWithFee = this.tx.vout.map(v => {
|
let voutWithFee = this.tx.vout.map((v, i) => {
|
||||||
return {
|
return {
|
||||||
type: v.scriptpubkey_type === 'fee' ? 'fee' : 'output',
|
type: v.scriptpubkey_type === 'fee' ? 'fee' : 'output',
|
||||||
value: v?.value,
|
value: v?.value,
|
||||||
address: v?.scriptpubkey_address || v?.scriptpubkey_type?.toUpperCase(),
|
address: v?.scriptpubkey_address || v?.scriptpubkey_type?.toUpperCase(),
|
||||||
|
index: i,
|
||||||
pegout: v?.pegout?.scriptpubkey_address,
|
pegout: v?.pegout?.scriptpubkey_address,
|
||||||
confidential: (this.isLiquid && v?.value === undefined),
|
confidential: (this.isLiquid && v?.value === undefined),
|
||||||
} as Xput;
|
} as Xput;
|
||||||
@ -91,11 +140,12 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
|||||||
}
|
}
|
||||||
const outputCount = voutWithFee.length;
|
const outputCount = voutWithFee.length;
|
||||||
|
|
||||||
let truncatedInputs = this.tx.vin.map(v => {
|
let truncatedInputs = this.tx.vin.map((v, i) => {
|
||||||
return {
|
return {
|
||||||
type: 'input',
|
type: 'input',
|
||||||
value: v?.prevout?.value,
|
value: v?.prevout?.value,
|
||||||
address: v?.prevout?.scriptpubkey_address || v?.prevout?.scriptpubkey_type?.toUpperCase(),
|
address: v?.prevout?.scriptpubkey_address || v?.prevout?.scriptpubkey_type?.toUpperCase(),
|
||||||
|
index: i,
|
||||||
coinbase: v?.is_coinbase,
|
coinbase: v?.is_coinbase,
|
||||||
pegin: v?.is_pegin,
|
pegin: v?.is_pegin,
|
||||||
confidential: (this.isLiquid && v?.prevout?.value === undefined),
|
confidential: (this.isLiquid && v?.prevout?.value === undefined),
|
||||||
@ -306,8 +356,7 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
|||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
this.hoverLine = {
|
this.hoverLine = {
|
||||||
...this.outputData[index],
|
...this.outputData[index]
|
||||||
index
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -315,4 +364,29 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
|||||||
onBlur(event, side, index): void {
|
onBlur(event, side, index): void {
|
||||||
this.hoverLine = null;
|
this.hoverLine = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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], {
|
||||||
|
queryParamsHandling: 'merge',
|
||||||
|
fragment: 'flow'
|
||||||
|
});
|
||||||
|
} 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], {
|
||||||
|
queryParamsHandling: 'merge',
|
||||||
|
fragment: 'flow'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.selectOutput.emit(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Inject, Injectable, PLATFORM_ID } from '@angular/core';
|
import { Inject, Injectable, PLATFORM_ID, LOCALE_ID } from '@angular/core';
|
||||||
import { ReplaySubject, BehaviorSubject, Subject, fromEvent, Observable } from 'rxjs';
|
import { ReplaySubject, BehaviorSubject, Subject, fromEvent, Observable } from 'rxjs';
|
||||||
import { Transaction } from '../interfaces/electrs.interface';
|
import { Transaction } from '../interfaces/electrs.interface';
|
||||||
import { IBackendInfo, MempoolBlock, MempoolBlockWithTransactions, MempoolBlockDelta, MempoolInfo, Recommendedfees, ReplacedTransaction, TransactionStripped } from '../interfaces/websocket.interface';
|
import { IBackendInfo, MempoolBlock, MempoolBlockWithTransactions, MempoolBlockDelta, MempoolInfo, Recommendedfees, ReplacedTransaction, TransactionStripped } from '../interfaces/websocket.interface';
|
||||||
@ -113,6 +113,7 @@ export class StateService {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(PLATFORM_ID) private platformId: any,
|
@Inject(PLATFORM_ID) private platformId: any,
|
||||||
|
@Inject(LOCALE_ID) private locale: string,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private storageService: StorageService,
|
private storageService: StorageService,
|
||||||
) {
|
) {
|
||||||
@ -151,7 +152,10 @@ export class StateService {
|
|||||||
|
|
||||||
this.blockVSize = this.env.BLOCK_WEIGHT_UNITS / 4;
|
this.blockVSize = this.env.BLOCK_WEIGHT_UNITS / 4;
|
||||||
|
|
||||||
this.timeLtr = new BehaviorSubject<boolean>(this.storageService.getValue('time-preference-ltr') === 'true');
|
const savedTimePreference = this.storageService.getValue('time-preference-ltr');
|
||||||
|
const rtlLanguage = (this.locale.startsWith('ar') || this.locale.startsWith('fa') || this.locale.startsWith('he'));
|
||||||
|
// default time direction is right-to-left, unless locale is a RTL language
|
||||||
|
this.timeLtr = new BehaviorSubject<boolean>(savedTimePreference === 'true' || (savedTimePreference == null && rtlLanguage));
|
||||||
this.timeLtr.subscribe((ltr) => {
|
this.timeLtr.subscribe((ltr) => {
|
||||||
this.storageService.setValue('time-preference-ltr', ltr ? 'true' : 'false');
|
this.storageService.setValue('time-preference-ltr', ltr ? 'true' : 'false');
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user